diff --git a/src/Speckle.Objects/Exceptions/TransformationException.cs b/src/Speckle.Objects/Exceptions/TransformationException.cs new file mode 100644 index 00000000..7076b295 --- /dev/null +++ b/src/Speckle.Objects/Exceptions/TransformationException.cs @@ -0,0 +1,17 @@ +using Speckle.Sdk; + +namespace Speckle.Objects.Exceptions; + +/// +/// object failed to transform +/// +public sealed class TransformationException : SpeckleException +{ + public TransformationException() { } + + public TransformationException(string? message) + : base(message) { } + + public TransformationException(string? message, Exception? innerException) + : base(message, innerException) { } +} diff --git a/src/Speckle.Objects/Geometry/Arc.cs b/src/Speckle.Objects/Geometry/Arc.cs index 6cb3fd20..85a8b4c3 100644 --- a/src/Speckle.Objects/Geometry/Arc.cs +++ b/src/Speckle.Objects/Geometry/Arc.cs @@ -88,13 +88,13 @@ public class Arc : Base, IHasBoundingBox, ICurve, ITransformable public Box? bbox { get; set; } /// - public bool TransformTo(Transform transform, out Arc transformed) + public Arc TransformTo(Transform transform) { - startPoint.TransformTo(transform, out Point transformedStartPoint); - midPoint.TransformTo(transform, out Point transformedMidpoint); - endPoint.TransformTo(transform, out Point transformedEndPoint); - plane.TransformTo(transform, out Plane pln); - Arc arc = new() + var transformedStartPoint = startPoint.TransformTo(transform); + var transformedMidpoint = midPoint.TransformTo(transform); + var transformedEndPoint = endPoint.TransformTo(transform); + var pln = plane.TransformTo(transform); + Arc transformed = new() { startPoint = transformedStartPoint, endPoint = transformedEndPoint, @@ -103,16 +103,7 @@ public bool TransformTo(Transform transform, out Arc transformed) domain = domain, units = units, }; - transformed = arc; - return true; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Arc arc); - transformed = arc; - return res; + return transformed; } /// diff --git a/src/Speckle.Objects/Geometry/Brep.cs b/src/Speckle.Objects/Geometry/Brep.cs index df201c32..b5a5552f 100644 --- a/src/Speckle.Objects/Geometry/Brep.cs +++ b/src/Speckle.Objects/Geometry/Brep.cs @@ -1,5 +1,6 @@ using System.Runtime.Serialization; using Speckle.Newtonsoft.Json; +using Speckle.Objects.Exceptions; using Speckle.Objects.Other; using Speckle.Objects.Primitive; using Speckle.Sdk.Common; @@ -434,37 +435,38 @@ public List FacesValue public double volume { get; set; } /// - public bool TransformTo(Transform transform, out Brep transformed) + public Brep TransformTo(Transform transform) { // transform display values var displayValues = new List(displayValue.Count); foreach (Mesh v in displayValue) { - v.TransformTo(transform, out Mesh mesh); - displayValues.Add(mesh); + var transformedMesh = v.TransformTo(transform); + displayValues.Add(transformedMesh); } // transform surfaces var surfaces = new List(Surfaces.Count); foreach (var srf in Surfaces) { - srf.TransformTo(transform, out Surface surface); - surfaces.Add(surface); + var transformedSurface = srf.TransformTo(transform); + surfaces.Add(transformedSurface); } // transform curve3d - var success3D = true; var transformedCurve3D = new List(); foreach (var curve in Curve3D) { - if (curve is ITransformable c) + if (curve is ITransformable c) { - c.TransformTo(transform, out ITransformable tc); - transformedCurve3D.Add((ICurve)tc); + var tc = c.TransformTo(transform); + transformedCurve3D.Add(tc); } else { - success3D = false; + throw new TransformationException( + $"BREP could not be transformed because it contains {curve.GetType()} curves that are not transformable" + ); } } @@ -472,11 +474,11 @@ public bool TransformTo(Transform transform, out Brep transformed) var transformedVertices = new List(Vertices.Count); foreach (var vertex in Vertices) { - vertex.TransformTo(transform, out Point transformedVertex); + Point transformedVertex = vertex.TransformTo(transform); transformedVertices.Add(transformedVertex); } - transformed = new Brep + var transformed = new Brep { units = units, displayValue = displayValues, @@ -556,15 +558,7 @@ public bool TransformTo(Transform transform, out Brep transformed) ); } - return success3D; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Brep brep); - transformed = brep; - return res; + return transformed; } [OnDeserialized] diff --git a/src/Speckle.Objects/Geometry/ControlPoint.cs b/src/Speckle.Objects/Geometry/ControlPoint.cs index fd942e35..0deb3852 100644 --- a/src/Speckle.Objects/Geometry/ControlPoint.cs +++ b/src/Speckle.Objects/Geometry/ControlPoint.cs @@ -40,18 +40,15 @@ public ControlPoint(double x, double y, double z, double w, string units, string } } - public bool TransformTo(Transform transform, out ControlPoint transformed) + public override Point TransformTo(Transform transform) { - TransformTo(transform, out Point transformedPoint); - transformed = new ControlPoint( - transformedPoint.x, - transformedPoint.y, - transformedPoint.z, - weight, - units, - applicationId - ); - return true; + return ((ITransformable)this).TransformTo(transform); + } + + ControlPoint ITransformable.TransformTo(Transform transform) + { + Point transformedPoint = base.TransformTo(transform); + return new ControlPoint(transformedPoint.x, transformedPoint.y, transformedPoint.z, weight, units, applicationId); } public override string ToString() diff --git a/src/Speckle.Objects/Geometry/Curve.cs b/src/Speckle.Objects/Geometry/Curve.cs index bd14e3ac..da871935 100644 --- a/src/Speckle.Objects/Geometry/Curve.cs +++ b/src/Speckle.Objects/Geometry/Curve.cs @@ -57,41 +57,34 @@ public class Curve : Base, ICurve, IHasBoundingBox, IHasArea, ITransformable - public bool TransformTo(Transform transform, out Curve transformed) + public Curve TransformTo(Transform transform) { // transform points - var transformedPoints = new List(); - foreach (var point in GetPoints()) + var originalPoints = GetPoints(); + var transformedPoints = new List(originalPoints.Count * 3); + foreach (var point in originalPoints) { - point.TransformTo(transform, out Point transformedPoint); - transformedPoints.Add(transformedPoint); + Point transformedPoint = point.TransformTo(transform); + transformedPoints.Add(transformedPoint.x); + transformedPoints.Add(transformedPoint.y); + transformedPoints.Add(transformedPoint.z); } - var result = displayValue.TransformTo(transform, out ITransformable polyline); - transformed = new Curve + var transformedPolyline = displayValue.TransformTo(transform); + return new Curve { degree = degree, periodic = periodic, rational = rational, - points = transformedPoints.SelectMany(o => o.ToList()).ToList(), + points = transformedPoints, weights = weights, knots = knots, - displayValue = (Polyline)polyline, + displayValue = transformedPolyline, closed = closed, units = units, applicationId = applicationId, domain = domain != null ? new Interval { start = domain.start, end = domain.end } : Interval.UnitInterval, }; - - return result; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Curve curve); - transformed = curve; - return res; } /// as list of s diff --git a/src/Speckle.Objects/Geometry/Line.cs b/src/Speckle.Objects/Geometry/Line.cs index 2fe90022..0801d9fd 100644 --- a/src/Speckle.Objects/Geometry/Line.cs +++ b/src/Speckle.Objects/Geometry/Line.cs @@ -47,11 +47,11 @@ public Line(IList coordinates, string units, string? applicationId = nul public Box? bbox { get; set; } - public bool TransformTo(Transform transform, out Line transformed) + public Line TransformTo(Transform transform) { - start.TransformTo(transform, out Point transformedStart); - end.TransformTo(transform, out Point transformedEnd); - transformed = new Line + Point transformedStart = start.TransformTo(transform); + Point transformedEnd = end.TransformTo(transform); + return new Line { start = transformedStart, end = transformedEnd, @@ -59,14 +59,6 @@ public bool TransformTo(Transform transform, out Line transformed) units = units, domain = new() { start = domain.start, end = domain.end }, }; - return true; - } - - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Line line); - transformed = line; - return res; } public List ToList() diff --git a/src/Speckle.Objects/Geometry/Mesh.cs b/src/Speckle.Objects/Geometry/Mesh.cs index 64079c69..3d4dc8f8 100644 --- a/src/Speckle.Objects/Geometry/Mesh.cs +++ b/src/Speckle.Objects/Geometry/Mesh.cs @@ -44,7 +44,7 @@ public bool Transform(Transform transform) vertices = GetPoints() .SelectMany(vertex => { - vertex.TransformTo(transform, out Point transformedVertex); + var transformedVertex = vertex.TransformTo(transform); return transformedVertex.ToList(); }) .ToList(); @@ -53,36 +53,29 @@ public bool Transform(Transform transform) } /// - public bool TransformTo(Transform transform, out Mesh transformed) + public Mesh TransformTo(Transform transform) { // transform vertices - var transformedVertices = new List(); - foreach (var vertex in GetPoints()) + var originalVertices = GetPoints(); + var transformedVertices = new List(originalVertices.Count * 3); + foreach (var vertex in originalVertices) { - vertex.TransformTo(transform, out Point transformedVertex); - transformedVertices.Add(transformedVertex); + Point transformedVertex = vertex.TransformTo(transform); + transformedVertices.Add(transformedVertex.x); + transformedVertices.Add(transformedVertex.y); + transformedVertices.Add(transformedVertex.z); } - transformed = new Mesh + return new Mesh { - vertices = transformedVertices.SelectMany(o => o.ToList()).ToList(), + vertices = transformedVertices, textureCoordinates = textureCoordinates, applicationId = applicationId ?? id, faces = faces, colors = colors, units = units, + ["renderMaterial"] = this["renderMaterial"], }; - transformed["renderMaterial"] = this["renderMaterial"]; - - return true; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Mesh brep); - transformed = brep; - return res; } #region Convenience Methods diff --git a/src/Speckle.Objects/Geometry/Plane.cs b/src/Speckle.Objects/Geometry/Plane.cs index 2da4b1bf..1ff9fb16 100644 --- a/src/Speckle.Objects/Geometry/Plane.cs +++ b/src/Speckle.Objects/Geometry/Plane.cs @@ -37,13 +37,13 @@ public class Plane : Base, ITransformable public required string units { get; set; } /// - public bool TransformTo(Transform transform, out Plane transformed) + public Plane TransformTo(Transform transform) { - origin.TransformTo(transform, out Point transformedOrigin); - normal.TransformTo(transform, out Vector transformedNormal); - xdir.TransformTo(transform, out Vector transformedXdir); - ydir.TransformTo(transform, out Vector transformedYdir); - transformed = new Plane + var transformedOrigin = origin.TransformTo(transform); + var transformedNormal = normal.TransformTo(transform); + var transformedXdir = xdir.TransformTo(transform); + var transformedYdir = ydir.TransformTo(transform); + return new Plane { origin = transformedOrigin, normal = transformedNormal, @@ -52,16 +52,6 @@ public bool TransformTo(Transform transform, out Plane transformed) applicationId = applicationId, units = units, }; - - return true; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Plane plane); - transformed = plane; - return res; } /// diff --git a/src/Speckle.Objects/Geometry/Point.cs b/src/Speckle.Objects/Geometry/Point.cs index 9911ce73..edbd2e65 100644 --- a/src/Speckle.Objects/Geometry/Point.cs +++ b/src/Speckle.Objects/Geometry/Point.cs @@ -58,7 +58,7 @@ public Point(double x, double y, double z, string units, string? applicationId = public required string units { get; set; } /// - public bool TransformTo(Transform transform, out Point transformed) + public virtual Point TransformTo(Transform transform) { var matrix = transform.matrix; @@ -68,16 +68,7 @@ public bool TransformTo(Transform transform, out Point transformed) var y = (this.x * matrix.M21 + this.y * matrix.M22 + this.z * matrix.M23 + unitFactor * matrix.M24) / divisor; var z = (this.x * matrix.M31 + this.y * matrix.M32 + this.z * matrix.M33 + unitFactor * matrix.M34) / divisor; - transformed = new Point(x, y, z, units, applicationId); - return true; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Point pt); - transformed = pt; - return res; + return new Point(x, y, z, units, applicationId); } /// diff --git a/src/Speckle.Objects/Geometry/Pointcloud.cs b/src/Speckle.Objects/Geometry/Pointcloud.cs index ac76f174..fa523e5f 100644 --- a/src/Speckle.Objects/Geometry/Pointcloud.cs +++ b/src/Speckle.Objects/Geometry/Pointcloud.cs @@ -39,34 +39,27 @@ public class Pointcloud : Base, IHasBoundingBox, ITransformable public Box? bbox { get; set; } /// - public bool TransformTo(Transform transform, out Pointcloud transformed) + public Pointcloud TransformTo(Transform transform) { // transform points - var transformedPoints = new List(); - foreach (var point in GetPoints()) + var originalPoints = GetPoints(); + var transformedPoints = new List(originalPoints.Count * 3); + foreach (var point in originalPoints) { - point.TransformTo(transform, out Point transformedPoint); - transformedPoints.Add(transformedPoint); + var transformedPoint = point.TransformTo(transform); + transformedPoints.Add(transformedPoint.x); + transformedPoints.Add(transformedPoint.y); + transformedPoints.Add(transformedPoint.z); } - transformed = new Pointcloud + return new Pointcloud { units = units, - points = transformedPoints.SelectMany(o => o.ToList()).ToList(), + points = transformedPoints, colors = colors, sizes = sizes, applicationId = applicationId, }; - - return true; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Pointcloud pc); - transformed = pc; - return res; } /// as list of s diff --git a/src/Speckle.Objects/Geometry/Polycurve.cs b/src/Speckle.Objects/Geometry/Polycurve.cs index d081b651..6590b4c2 100644 --- a/src/Speckle.Objects/Geometry/Polycurve.cs +++ b/src/Speckle.Objects/Geometry/Polycurve.cs @@ -1,3 +1,4 @@ +using Speckle.Objects.Exceptions; using Speckle.Objects.Other; using Speckle.Objects.Primitive; using Speckle.Sdk.Common; @@ -9,7 +10,7 @@ namespace Speckle.Objects.Geometry; /// A curve that is comprised of multiple curves connected. /// [SpeckleType("Objects.Geometry.Polycurve")] -public class Polycurve : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable +public class Polycurve : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable { /// /// Gets or sets the list of segments that comprise this @@ -43,33 +44,29 @@ public class Polycurve : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable public Box? bbox { get; set; } /// - public bool TransformTo(Transform transform, out ITransformable polycurve) + public Polycurve TransformTo(Transform transform) { - // transform segments - var success = true; var transformed = new List(); - foreach (var curve in segments) + foreach (var segment in segments) { - if (curve is ITransformable c) + if (segment is not ITransformable c) { - c.TransformTo(transform, out ITransformable tc); - transformed.Add((ICurve)tc); - } - else - { - success = false; + throw new TransformationException( + $"Polycurve could not be transformed because it contains {segment.GetType()} segments that are not transformable" + ); } + + var tc = c.TransformTo(transform); + transformed.Add(tc); } - polycurve = new Polycurve + return new Polycurve { segments = transformed, applicationId = applicationId, closed = closed, units = units, }; - - return success; } /// diff --git a/src/Speckle.Objects/Geometry/Polyline.cs b/src/Speckle.Objects/Geometry/Polyline.cs index 6c3dac9f..5354e03b 100644 --- a/src/Speckle.Objects/Geometry/Polyline.cs +++ b/src/Speckle.Objects/Geometry/Polyline.cs @@ -10,7 +10,7 @@ namespace Speckle.Objects.Geometry; /// A polyline curve, defined by a set of vertices. /// [SpeckleType("Objects.Geometry.Polyline")] -public class Polyline : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable +public class Polyline : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable { /// /// Gets or sets the raw coordinates that define this polyline. Use GetPoints instead to access this data as instances instead. @@ -44,25 +44,26 @@ public class Polyline : Base, ICurve, IHasArea, IHasBoundingBox, ITransformable public Box? bbox { get; set; } /// - public bool TransformTo(Transform transform, out ITransformable transformed) + public Polyline TransformTo(Transform transform) { // transform points - var transformedPoints = new List(); - foreach (var point in GetPoints()) + var originalPoints = GetPoints(); + var transformedPoints = new List(originalPoints.Count * 3); + foreach (var point in originalPoints) { - point.TransformTo(transform, out Point transformedPoint); - transformedPoints.Add(transformedPoint); + Point transformedPoint = point.TransformTo(transform); + transformedPoints.Add(transformedPoint.x); + transformedPoints.Add(transformedPoint.y); + transformedPoints.Add(transformedPoint.z); } - transformed = new Polyline + return new Polyline { - value = transformedPoints.SelectMany(o => o.ToList()).ToList(), + value = transformedPoints, closed = closed, applicationId = applicationId, units = units, }; - - return true; } ///This function may be suboptimal for performance for polylines with many points diff --git a/src/Speckle.Objects/Geometry/Surface.cs b/src/Speckle.Objects/Geometry/Surface.cs index d97ed633..47d0cf4a 100644 --- a/src/Speckle.Objects/Geometry/Surface.cs +++ b/src/Speckle.Objects/Geometry/Surface.cs @@ -103,19 +103,20 @@ public Surface(IList pointData, int countU, int countV) public Box? bbox { get; set; } /// - public bool TransformTo(Transform transform, out Surface transformed) + public Surface TransformTo(Transform transform) { var ptMatrix = GetControlPoints(); foreach (var ctrlPts in ptMatrix) { for (int i = 0; i < ctrlPts.Count; i++) { - ctrlPts[i].TransformTo(transform, out var tPt); + ITransformable pt = ctrlPts[i]; + var tPt = pt.TransformTo(transform); ctrlPts[i] = tPt; } } - transformed = new Surface(ptMatrix) + return new Surface(ptMatrix) { degreeU = degreeU, degreeV = degreeV, @@ -130,16 +131,6 @@ public bool TransformTo(Transform transform, out Surface transformed) knotsV = knotsV, units = units, }; - - return true; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - var res = TransformTo(transform, out Surface surface); - transformed = surface; - return res; } /// diff --git a/src/Speckle.Objects/Geometry/Vector.cs b/src/Speckle.Objects/Geometry/Vector.cs index 83114648..93e2b355 100644 --- a/src/Speckle.Objects/Geometry/Vector.cs +++ b/src/Speckle.Objects/Geometry/Vector.cs @@ -64,22 +64,13 @@ public Vector(double x, double y, double z, string units, string? applicationId public Box? bbox { get; set; } /// - public bool TransformTo(Transform transform, out Vector transformed) + public Vector TransformTo(Transform transform) { var m = transform.matrix; var tX = x * m.M11 + y * m.M12 + z * m.M13; var tY = x * m.M21 + y * m.M22 + z * m.M23; var tZ = x * m.M31 + y * m.M32 + z * m.M33; - transformed = new Vector(tX, tY, tZ, units, applicationId); - return true; - } - - /// - public bool TransformTo(Transform transform, out ITransformable transformed) - { - _ = TransformTo(transform, out Vector vec); - transformed = vec; - return true; + return new Vector(tX, tY, tZ, units, applicationId); } /// diff --git a/src/Speckle.Objects/Interfaces.cs b/src/Speckle.Objects/Interfaces.cs index 43f6d52f..d6936601 100644 --- a/src/Speckle.Objects/Interfaces.cs +++ b/src/Speckle.Objects/Interfaces.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.Contracts; +using Speckle.Objects.Exceptions; using Speckle.Objects.Geometry; using Speckle.Objects.Other; using Speckle.Objects.Primitive; @@ -62,27 +64,24 @@ public interface ICurve : ISpeckleObject /// Generic Interface for transformable objects. /// /// The type of object to support transformations. -public interface ITransformable : ITransformable - where T : ITransformable -{ - /// - bool TransformTo(Transform transform, out T transformed); -} - -/// -/// Interface for transformable objects where the type may not be known on convert (eg ICurve implementations) -/// -public interface ITransformable : ISpeckleObject +public interface ITransformable : ITransformable + where T : ISpeckleObject { /// - /// Returns a copy of the object with it's coordinates transformed by the provided + /// Returns a copy of the object with its coordinates transformed by the provided /// /// The to be applied. - /// The transformed copy of the object. - /// True if the transform operation was successful, false otherwise. - bool TransformTo(Transform transform, out ITransformable transformed); + /// thrown if the object could not be transformed by the provided + /// The transformed copy of the object. + [Pure] + T TransformTo(Transform transform); } +/// +/// Do not inherit directly, inherit from instead +/// +public interface ITransformable : ISpeckleObject { } + /// /// Specifies displayable simple geometries to be used as a fallback /// if a displayable form cannot be converted. diff --git a/src/Speckle.Objects/TransformableExtensions.cs b/src/Speckle.Objects/TransformableExtensions.cs new file mode 100644 index 00000000..caed4a03 --- /dev/null +++ b/src/Speckle.Objects/TransformableExtensions.cs @@ -0,0 +1,32 @@ +using System.Diagnostics.CodeAnalysis; +using Speckle.Objects.Other; + +namespace Speckle.Objects; + +public static class TransformableExtensions +{ + /// + public static ITransformable TransformTo(this ITransformable transformable, Transform transform) + { + return ((ITransformable)transformable).TransformTo(transform); + } + + public static bool TransformTo( + this ITransformable transformable, + Transform transform, + [NotNull] out T? transformed + ) + where T : class, ITransformable + { + // try + // { + transformed = (T)transformable.TransformTo(transform); + return true; + // } + // catch (TransformationException) + // { + // transformed = null; + // return false; + // } + } +} diff --git a/tests/Speckle.Objects.Tests.Unit/Geometry/VectorTests.cs b/tests/Speckle.Objects.Tests.Unit/Geometry/VectorTests.cs new file mode 100644 index 00000000..dae91baf --- /dev/null +++ b/tests/Speckle.Objects.Tests.Unit/Geometry/VectorTests.cs @@ -0,0 +1,40 @@ +using Speckle.Objects.Geometry; +using Xunit; + +namespace Speckle.Objects.Tests.Unit.Geometry; + +public class VectorTests +{ + [Theory] + [InlineData(1d, 2d, 3d, "m")] + [InlineData(100d, 0d, -200d, "ft")] + public void TestConstruction(double x, double y, double z, string units) + { + var pctor = new Vector(x, y, z, units); + + var init = new Vector + { + x = x, + y = y, + z = z, + units = units, + }; + + Assert.Equal(pctor.x, init.x); + Assert.Equal(pctor.y, init.y); + Assert.Equal(pctor.z, init.z); + Assert.Equal(pctor.units, init.units); + } + + [Theory] + [InlineData(1d, 0d, 0d, 1d)] + [InlineData(0d, 2d, 0d, 2d)] + [InlineData(0d, 0d, -3d, 3d)] + [InlineData(1d, 1d, 0d, 1.4142135623730951d)] + public void TestLength(double x, double y, double z, double expected) + { + var testCase = new Vector(x, y, z, ""); + var actual = testCase.Length; + Assert.Equal(actual, expected); + } +}