From f91081f231401715f2aebab1cd03a2341ed39e07 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Sat, 6 Apr 2024 23:17:13 +0200 Subject: [PATCH 1/2] Add support for bounding box --- src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj | 3 +- src/IPA.Bcfier.Revit/Installer.iss | 1 + .../OpenProject/AxisAlignedBoundingBox.cs | 67 +++++++ .../OpenProject/BcfExtensions.cs | 131 +++++++++++++ .../OpenProject/ClippingPlaneUtils.cs | 79 ++++++++ .../OpenProject/Extensions.cs | 45 +++++ src/IPA.Bcfier.Revit/OpenProject/Position.cs | 51 +++++ .../OpenProject/ProjectPositionWrapper.cs | 66 +++++++ .../OpenProject/RevitUtils.cs | 176 ++++++++++++++++++ src/IPA.Bcfier.Revit/OpenProject/Vector3.cs | 92 +++++++++ .../Services/RevitViewpointCreationService.cs | 19 ++ .../Services/RevitViewpointDisplayService.cs | 45 ++++- 12 files changed, 773 insertions(+), 2 deletions(-) create mode 100644 src/IPA.Bcfier.Revit/OpenProject/AxisAlignedBoundingBox.cs create mode 100644 src/IPA.Bcfier.Revit/OpenProject/BcfExtensions.cs create mode 100644 src/IPA.Bcfier.Revit/OpenProject/ClippingPlaneUtils.cs create mode 100644 src/IPA.Bcfier.Revit/OpenProject/Extensions.cs create mode 100644 src/IPA.Bcfier.Revit/OpenProject/Position.cs create mode 100644 src/IPA.Bcfier.Revit/OpenProject/ProjectPositionWrapper.cs create mode 100644 src/IPA.Bcfier.Revit/OpenProject/RevitUtils.cs create mode 100644 src/IPA.Bcfier.Revit/OpenProject/Vector3.cs diff --git a/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj index 1da7d6df..f13ed197 100644 --- a/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj +++ b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj @@ -18,7 +18,8 @@ - + + diff --git a/src/IPA.Bcfier.Revit/Installer.iss b/src/IPA.Bcfier.Revit/Installer.iss index 1ea56390..dfee2e37 100644 --- a/src/IPA.Bcfier.Revit/Installer.iss +++ b/src/IPA.Bcfier.Revit/Installer.iss @@ -33,6 +33,7 @@ Name: revit24; Description: Addin for Autodesk Revit 2024; Types: full [Files] ;REVIT 2024 +Source: "{#Repository}\DecimalEx.dll"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 Source: "{#Repository}\Dangl.BCF.dll"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 Source: "{#Repository}\IPA.Bcfier.dll"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 Source: "{#Repository}\IPA.Bcfier.Revit.dll"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 diff --git a/src/IPA.Bcfier.Revit/OpenProject/AxisAlignedBoundingBox.cs b/src/IPA.Bcfier.Revit/OpenProject/AxisAlignedBoundingBox.cs new file mode 100644 index 00000000..4caa67f7 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/AxisAlignedBoundingBox.cs @@ -0,0 +1,67 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +namespace IPA.Bcfier.Revit.OpenProject +{ + /// + /// Immutable implementation of an axis aligned bounding box with decimal precision. + /// + public sealed class AxisAlignedBoundingBox : IEquatable + { + /// + /// A bounding box with max values, representing an infinite bounding box + /// + public static AxisAlignedBoundingBox Infinite => + new AxisAlignedBoundingBox(Vector3.InfiniteMin, Vector3.InfiniteMax); + + /// + /// This min corner of the axis aligned bounding box. + /// + public Vector3 Min { get; } + + /// + /// This min corner of the axis aligned bounding box. + /// + public Vector3 Max { get; } + + public AxisAlignedBoundingBox(Vector3 min, Vector3 max) + { + Min = min; + Max = max; + } + + /// + /// Merges this bounding box with the given one and returns the resulting bounding box, that + /// is included in both bounding boxes. If there is no intersection between both bounding + /// boxes, an infinite bounding box is returned. + /// + /// The other bounding box + /// The merged bounding box. + public AxisAlignedBoundingBox MergeReduce(AxisAlignedBoundingBox box) + { + var min = new Vector3( + Math.Max(Min.X, box.Min.X), + Math.Max(Min.Y, box.Min.Y), + Math.Max(Min.Z, box.Min.Z)); + var max = new Vector3( + Math.Min(Max.X, box.Max.X), + Math.Min(Max.Y, box.Max.Y), + Math.Min(Max.Z, box.Max.Z)); + + // validity check + if (min.X < max.X && min.Y < max.Y && min.Z < max.Z) + return new AxisAlignedBoundingBox(min, max); + + return Infinite; + } + + /// + public bool Equals(AxisAlignedBoundingBox other) + { + if (other == null) + return false; + + return Min.Equals(other.Min) && Max.Equals(other.Max); + } + } +} diff --git a/src/IPA.Bcfier.Revit/OpenProject/BcfExtensions.cs b/src/IPA.Bcfier.Revit/OpenProject/BcfExtensions.cs new file mode 100644 index 00000000..847526a1 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/BcfExtensions.cs @@ -0,0 +1,131 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +using IPA.Bcfier.Models.Bcf; + +namespace IPA.Bcfier.Revit.OpenProject +{ + public static class BcfExtensions + { + public static Vector3 ToVector3(this BcfViewpointVector direction) => + new Vector3( + direction.X.ToDecimal(), + direction.Y.ToDecimal(), + direction.Z.ToDecimal()); + + public static Vector3 ToVector3(this BcfViewpointPoint point) => + new Vector3( + point.X.ToDecimal(), + point.Y.ToDecimal(), + point.Z.ToDecimal()); + + /// + /// Converts a axis aligned bounding box into a list of bcf api clipping planes. + /// + /// + /// The bounding box that defines the clipping. Can contain infinite values, which are + /// interpreted as if the view is not clipped in that direction. + /// + /// + /// An optional clipping center. Important for positioning the clipping planes not too far + /// away from the model. If no clipping center is given, the center of the clipping box is + /// used, which can result in very odd clipping plane locations, if the clipping box + /// contains infinite values. + /// + /// A list of clipping planes. + public static List ToClippingPlanes( + this AxisAlignedBoundingBox clippingBox, + Vector3 clippingCenter = null) + { + Vector3 center = clippingCenter ?? (clippingBox.Min + clippingBox.Max) * 0.5m; + + var planes = new List(); + + if (clippingBox.Min.X.IsFinite()) + { + planes.Add(new BcfViewpointClippingPlane + { + Location = new BcfViewpointPoint + { + X = Convert.ToSingle(clippingBox.Min.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new BcfViewpointVector { X = -1, Y = 0, Z = 0 } + }); + } + + if (clippingBox.Min.Y.IsFinite()) + { + planes.Add(new BcfViewpointClippingPlane + { + Location = new BcfViewpointPoint + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(clippingBox.Min.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new BcfViewpointVector { X = 0, Y = -1, Z = 0 } + }); + } + + if (clippingBox.Min.Z.IsFinite()) + { + planes.Add(new BcfViewpointClippingPlane + { + Location = new BcfViewpointPoint + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(clippingBox.Min.Z) + }, + Direction = new BcfViewpointVector { X = 0, Y = 0, Z = -1 } + }); + } + + if (clippingBox.Max.X.IsFinite()) + { + planes.Add(new BcfViewpointClippingPlane + { + Location = new BcfViewpointPoint + { + X = Convert.ToSingle(clippingBox.Max.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new BcfViewpointVector { X = 1, Y = 0, Z = 0 } + }); + } + + if (clippingBox.Max.Y.IsFinite()) + { + planes.Add(new BcfViewpointClippingPlane + { + Location = new BcfViewpointPoint + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(clippingBox.Max.Y), + Z = Convert.ToSingle(center.Z) + }, + Direction = new BcfViewpointVector { X = 0, Y = 1, Z = 0 } + }); + } + + if (clippingBox.Max.Z.IsFinite()) + { + planes.Add(new BcfViewpointClippingPlane + { + Location = new BcfViewpointPoint + { + X = Convert.ToSingle(center.X), + Y = Convert.ToSingle(center.Y), + Z = Convert.ToSingle(clippingBox.Max.Z) + }, + Direction = new BcfViewpointVector { X = 0, Y = 0, Z = 1 } + }); + } + + return planes; + } + } +} diff --git a/src/IPA.Bcfier.Revit/OpenProject/ClippingPlaneUtils.cs b/src/IPA.Bcfier.Revit/OpenProject/ClippingPlaneUtils.cs new file mode 100644 index 00000000..d9bd5aa5 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/ClippingPlaneUtils.cs @@ -0,0 +1,79 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +using IPA.Bcfier.Models.Bcf; + +namespace IPA.Bcfier.Revit.OpenProject +{ + public static class ClippingPlaneUtils + { + private static Vector3 XPositive() => new Vector3(1, 0, 0); + + private static Vector3 XNegative() => new Vector3(-1, 0, 0); + + private static Vector3 YPositive() => new Vector3(0, 1, 0); + + private static Vector3 YNegative() => new Vector3(0, -1, 0); + + private static Vector3 ZPositive() => new Vector3(0, 0, 1); + + private static Vector3 ZNegative() => new Vector3(0, 0, -1); + + /// + /// Converts a bcf clipping plane into an axis aligned clipping bounding box. The clipping + /// plane direction vector (plane normal) is compared to the coordinate axes. If the angle + /// is below a given delta, the plane is converted to one side of an axis aligned clipping + /// bounding box. The rest of the box's sides are set to max value. + /// + /// The bcf clipping plane. + /// + /// The maximum angle the plane direction can differ from a XYZ coordinate axis. + /// + /// The axis aligned bounding box. + public static AxisAlignedBoundingBox ToAxisAlignedBoundingBox(this BcfViewpointClippingPlane plane, decimal delta = 0) + { + var infiniteMax = new Vector3(decimal.MaxValue, decimal.MaxValue, decimal.MaxValue); + var infiniteMin = new Vector3(decimal.MinValue, decimal.MinValue, decimal.MinValue); + + var normal = new Vector3( + (decimal)plane.Direction.X, + (decimal)plane.Direction.Y, + (decimal)plane.Direction.Z); + + var boundingBox = new AxisAlignedBoundingBox(infiniteMin, infiniteMax); + + if (normal.AngleBetween(ZPositive()) < delta) + { + var max = new Vector3(decimal.MaxValue, decimal.MaxValue, (decimal)plane.Location.Z); + boundingBox = new AxisAlignedBoundingBox(infiniteMin, max); + } + else if (normal.AngleBetween(ZNegative()) < delta) + { + var min = new Vector3(decimal.MinValue, decimal.MinValue, (decimal)plane.Location.Z); + boundingBox = new AxisAlignedBoundingBox(min, infiniteMax); + } + else if (normal.AngleBetween(YPositive()) < delta) + { + var max = new Vector3(decimal.MaxValue, (decimal)plane.Location.Y, decimal.MaxValue); + boundingBox = new AxisAlignedBoundingBox(infiniteMin, max); + } + else if (normal.AngleBetween(YNegative()) < delta) + { + var min = new Vector3(decimal.MinValue, (decimal)plane.Location.Y, decimal.MinValue); + boundingBox = new AxisAlignedBoundingBox(min, infiniteMax); + } + else if (normal.AngleBetween(XPositive()) < delta) + { + var max = new Vector3((decimal)plane.Location.X, decimal.MaxValue, decimal.MaxValue); + boundingBox = new AxisAlignedBoundingBox(infiniteMin, max); + } + else if (normal.AngleBetween(XNegative()) < delta) + { + var min = new Vector3((decimal)plane.Location.X, decimal.MinValue, decimal.MinValue); + boundingBox = new AxisAlignedBoundingBox(min, infiniteMax); + } + + return boundingBox; + } + } +} diff --git a/src/IPA.Bcfier.Revit/OpenProject/Extensions.cs b/src/IPA.Bcfier.Revit/OpenProject/Extensions.cs new file mode 100644 index 00000000..16792654 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/Extensions.cs @@ -0,0 +1,45 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +namespace IPA.Bcfier.Revit.OpenProject +{ + public static class Extensions + { + /// + /// Checks a decimal number against infinity. + /// + /// A decimal value + /// true, if a decimal value is finite, false otherwise + public static bool IsFinite(this decimal number) => number > decimal.MinValue && number < decimal.MaxValue; + + /// + /// Converts a double value to a decimal value without throwing a . If the double is bigger then the maximal value of decimals, + /// the decimal maximal value is returned. + /// + /// The double value + /// The converted decimal value + public static decimal ToDecimal(this double @double) + { + if (@double < 0) + return @double < (double)decimal.MinValue ? decimal.MinValue : (decimal)@double; + + return @double > (double)decimal.MaxValue ? decimal.MaxValue : (decimal)@double; + } + + /// + /// Converts a float value to a decimal value without throwing a . If the float is bigger then the maximal value of decimals, + /// the decimal maximal value is returned. + /// + /// The float value + /// The converted decimal value + public static decimal ToDecimal(this float @float) + { + if (@float < 0) + return @float < (float)decimal.MinValue ? decimal.MinValue : (decimal)@float; + + return @float > (float)decimal.MaxValue ? decimal.MaxValue : (decimal)@float; + } + } +} diff --git a/src/IPA.Bcfier.Revit/OpenProject/Position.cs b/src/IPA.Bcfier.Revit/OpenProject/Position.cs new file mode 100644 index 00000000..e283b906 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/Position.cs @@ -0,0 +1,51 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +namespace IPA.Bcfier.Revit.OpenProject +{ + /// + /// This immutable class describes a directed location in 3d space. It contains a center point + /// and a forward and up vector to describe a defined orientation in 3d space. + /// + public sealed class Position : IEquatable + { + /// + /// The center position vector. + /// + public Vector3 Center { get; } + + /// + /// The forward direction vector. + /// + public Vector3 Forward { get; } + + /// + /// The up direction vector. + /// + public Vector3 Up { get; } + + public Position() + { + Center = Vector3.Zero; + Forward = Vector3.Zero; + Up = Vector3.Zero; + } + + public Position(Vector3 center, Vector3 forward, Vector3 up) + { + Center = center; + Forward = forward; + Up = up; + } + + /// + public bool Equals(Position other) + { + if (other == null) return false; + + return Center.Equals(other.Center) && + Forward.Equals(other.Forward) && + Up.Equals(other.Up); + } + } +} diff --git a/src/IPA.Bcfier.Revit/OpenProject/ProjectPositionWrapper.cs b/src/IPA.Bcfier.Revit/OpenProject/ProjectPositionWrapper.cs new file mode 100644 index 00000000..6a234d9f --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/ProjectPositionWrapper.cs @@ -0,0 +1,66 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +using Autodesk.Revit.DB; + +namespace IPA.Bcfier.Revit.OpenProject +{ + /// + /// A immutable wrapper class around the + /// + public sealed class ProjectPositionWrapper + { + /// + /// The translation of the project into east-west, mostly translated into a translation into x-direction. + /// + public decimal EastWest { get; } + + /// + /// The translation of the project into north-south, mostly translated into a translation + /// into y-direction. + /// + public decimal NorthSouth { get; } + + /// + /// The elevation of the project, mostly translated into a translation into z-direction. + /// + public decimal Elevation { get; } + + /// + /// The angle of the project north to true north. + /// + public decimal Angle { get; } + + public ProjectPositionWrapper(decimal eastWest, decimal northSouth, decimal elevation, decimal angle) + { + EastWest = eastWest; + NorthSouth = northSouth; + Elevation = elevation; + Angle = angle; + } + + public ProjectPositionWrapper() + { + EastWest = 0; + NorthSouth = 0; + Elevation = 0; + Angle = 0; + } + + public ProjectPositionWrapper(ProjectPosition projectPosition) + { + EastWest = projectPosition.EastWest.ToDecimal(); + NorthSouth = projectPosition.NorthSouth.ToDecimal(); + Elevation = projectPosition.Elevation.ToDecimal(); + Angle = projectPosition.Angle.ToDecimal(); + } + + /// + /// Returns the project position's translation as a . The east-west + /// translation is taken as x, the north-south as y and the elevation as z. This represents + /// the coordinates of the project as an xyz-coordinate system. + /// + /// The project position's location as a vector. + public Vector3 GetTranslation() => new(EastWest, NorthSouth, Elevation); + } +} diff --git a/src/IPA.Bcfier.Revit/OpenProject/RevitUtils.cs b/src/IPA.Bcfier.Revit/OpenProject/RevitUtils.cs new file mode 100644 index 00000000..68366c75 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/RevitUtils.cs @@ -0,0 +1,176 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +using Autodesk.Revit.DB; + +namespace IPA.Bcfier.Revit.OpenProject +{ + public static class RevitUtils + { + /// + /// Converts a camera position according to a projects base position. It takes a position in + /// coordinates of the project base location and transform them to global coordinates or + /// does so in reverse, if the flag is set to true. + /// + /// The revit project base location + /// The source position + /// + /// Default is false, if set to true, transformation is done from global coordinates to + /// revit project base location coordinates + /// + /// The resulting position + public static Position TransformCameraPosition( + ProjectPositionWrapper projectBase, + Position position, + bool reverse = false) + { + var i = reverse ? -1 : 1; + + Vector3 translation = projectBase.GetTranslation() * i; + var rotation = i * projectBase.Angle; + + // do translation before rotation if we transform from global to local coordinates + Vector3 center = reverse + ? new Vector3( + position.Center.X + translation.X, + position.Center.Y + translation.Y, + position.Center.Z + translation.Z) + : position.Center; + + // rotation + var centerX = center.X * DecimalMath.DecimalEx.Cos(rotation) - center.Y * DecimalMath.DecimalEx.Sin(rotation); + var centerY = center.X * DecimalMath.DecimalEx.Sin(rotation) + center.Y * DecimalMath.DecimalEx.Cos(rotation); + + // do translation after rotation if we transform from local to global coordinates + Vector3 newCenter = reverse + ? new Vector3(centerX, centerY, center.Z) + : new Vector3(centerX + translation.X, centerY + translation.Y, center.Z + translation.Z); + + var forwardX = position.Forward.X * DecimalMath.DecimalEx.Cos(rotation) - + position.Forward.Y * DecimalMath.DecimalEx.Sin(rotation); + var forwardY = position.Forward.X * DecimalMath.DecimalEx.Sin(rotation) + + position.Forward.Y * DecimalMath.DecimalEx.Cos(rotation); + var newForward = new Vector3(forwardX, forwardY, position.Forward.Z); + + var upX = position.Up.X * DecimalMath.DecimalEx.Cos(rotation) - position.Up.Y * DecimalMath.DecimalEx.Sin(rotation); + var upY = position.Up.X * DecimalMath.DecimalEx.Sin(rotation) + position.Up.Y * DecimalMath.DecimalEx.Cos(rotation); + var newUp = new Vector3(upX, upY, position.Up.Z); + + return new Position(newCenter, newForward, newUp); + } + + /// + /// Converts a object into a + /// + /// The position object + /// the converted view orientation object + public static ViewOrientation3D ToViewOrientation3D(this Position position) => + new(position.Center.ToRevitXyz(), + position.Up.ToRevitXyz(), + position.Forward.ToRevitXyz()); + + /// + /// Converts a object into a + /// + /// The vector3 object + /// The Revit vector object + public static XYZ ToRevitXyz(this Vector3 vec) => + new(Convert.ToDouble(vec.X), + Convert.ToDouble(vec.Y), + Convert.ToDouble(vec.Z)); + + /// + /// Converts a object into a + /// + /// The vector3 object + /// The Revit vector object + public static Vector3 ToVector3(this XYZ vec) => new(vec.X.ToDecimal(), vec.Y.ToDecimal(), vec.Z.ToDecimal()); + + /// + /// Converts some basic revit view values to a view box height and a view box width. The + /// revit views are defined by coordinates in project space. + /// + /// The top right corner of the revit view. + /// The bottom left corner of the revit view. + /// The right direction of the revit view. + /// A tuple of the height and the width of the view box. + public static (double viewBoxHeight, double viewBoxWidth) ConvertToViewBoxValues( + XYZ topRight, XYZ bottomLeft, XYZ right) + { + XYZ diagonal = topRight.Subtract(bottomLeft); + var distance = topRight.DistanceTo(bottomLeft); + var angleBetweenBottomAndDiagonal = diagonal.AngleTo(right); + + var height = distance * Math.Sin(angleBetweenBottomAndDiagonal); + var width = distance * Math.Cos(angleBetweenBottomAndDiagonal); + + return (height, width); + } + + /// + /// Converts feet units to meters. Feet are the internal Revit units. + /// + /// Value in internal Revit units to be converted to meters + /// + public static double ToMeters(this double internalUnits) + { + return UnitUtils.ConvertFromInternalUnits(internalUnits, UnitTypeId.Meters); + } + + /// + /// Converts meters units to feet. Feet are the internal Revit units. + /// + /// Value in feet to be converted to feet + /// + public static double ToInternalRevitUnit(this double meters) + { + return UnitUtils.ConvertToInternalUnits(meters, UnitTypeId.Meters); + } + + /// + /// Converts a vector containing values in feet units to meter. Feet are the internal Revit units. + /// + /// The vector with values in feet + /// The vector with values in meter + public static Vector3 ToMeters(this Vector3 vec) + { + var x = vec.X.IsFinite() ? Convert.ToDouble(vec.X).ToMeters().ToDecimal() : vec.X; + var y = vec.Y.IsFinite() ? Convert.ToDouble(vec.Y).ToMeters().ToDecimal() : vec.Y; + var z = vec.Z.IsFinite() ? Convert.ToDouble(vec.Z).ToMeters().ToDecimal() : vec.Z; + + return new Vector3(x, y, z); + } + + /// + /// Converts a vector containing values in meter units to feet. Feet are the internal Revit units. + /// + /// The vector with values in meter + /// The vector with values in feet + public static Vector3 ToInternalUnits(this Vector3 vec) + { + var x = vec.X.IsFinite() ? Convert.ToDouble(vec.X).ToInternalRevitUnit().ToDecimal() : vec.X; + var y = vec.Y.IsFinite() ? Convert.ToDouble(vec.Y).ToInternalRevitUnit().ToDecimal() : vec.Y; + var z = vec.Z.IsFinite() ? Convert.ToDouble(vec.Z).ToInternalRevitUnit().ToDecimal() : vec.Z; + + return new Vector3(x, y, z); + } + + /// + /// Converts a position containing values in feet units to meter. Feet are the internal + /// Revit units. + /// + /// The position with values in feet + /// The position with values in meter + public static Position ToMeters(this Position pos) => + new(pos.Center.ToMeters(), pos.Forward.ToMeters(), pos.Up.ToMeters()); + + /// + /// Converts a position containing values in meter units to feet. Feet are the internal + /// Revit units. + /// + /// The position with values in meter + /// The position with values in feet + public static Position ToInternalUnits(this Position pos) => + new(pos.Center.ToInternalUnits(), pos.Forward.ToInternalUnits(), pos.Up.ToInternalUnits()); + } +} diff --git a/src/IPA.Bcfier.Revit/OpenProject/Vector3.cs b/src/IPA.Bcfier.Revit/OpenProject/Vector3.cs new file mode 100644 index 00000000..58ea7928 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenProject/Vector3.cs @@ -0,0 +1,92 @@ +// This was mostly taken from: +// https://github.com/opf/openproject-revit-add-in + +namespace IPA.Bcfier.Revit.OpenProject +{ + /// + /// A light-weight vector class for 3d vectors, containing values in decimal precision. + /// + public sealed class Vector3 : IEquatable + { + /// + /// A vector of max values, representing an positive infinite point. + /// + public static Vector3 InfiniteMax => + new Vector3(decimal.MaxValue, decimal.MaxValue, decimal.MaxValue); + + /// + /// A vector of min values, representing an negative infinite point. + /// + public static Vector3 InfiniteMin => + new Vector3(decimal.MinValue, decimal.MinValue, decimal.MinValue); + + /// + /// A vector of zero values, representing the zero vector. + /// + public static Vector3 Zero => new Vector3(0, 0, 0); + + public decimal X { get; } + public decimal Y { get; } + public decimal Z { get; } + + public Vector3(decimal x, decimal y, decimal z) + { + X = x; + Y = y; + Z = z; + } + + /// + /// Calculates the angle between this vector and the given one. + /// + /// The other vector. + /// The angle in radians. + public decimal AngleBetween(Vector3 vec) + { + if (Equals(Zero) || vec.Equals(Zero)) + throw new ArgumentException("no angle calculation possible for zero vectors"); + + return DecimalMath.DecimalEx.ACos(this * vec / (Euclidean() * vec.Euclidean())); + } + + /// + /// Calculates the sum of this vector and the given one. + /// + /// The left vector. + /// The right vector. + /// The dot product value. + public static Vector3 operator +(Vector3 v1, Vector3 v2) => new Vector3(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); + + /// + /// Calculates the dot product of this vector and the given one. + /// + /// The left vector. + /// The right vector. + /// The dot product value. + public static decimal operator *(Vector3 v1, Vector3 v2) => + v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; + + /// + /// Calculates the scalar product of this vector and a scalar value. + /// + /// The vector. + /// The scalar value. + /// The dot product value. + public static Vector3 operator *(Vector3 v1, decimal scalar) => + new Vector3(v1.X * scalar, v1.Y * scalar, v1.Z * scalar); + + /// + /// Calculates the euclidean distance of this vector. + /// + /// The euclidean distance. + public decimal Euclidean() => DecimalMath.DecimalEx.Sqrt(X * X + Y * Y + Z * Z); + + /// + public bool Equals(Vector3 other) + { + if (other == null) return false; + + return X == other.X && Y == other.Y && Z == other.Z; + } + } +} diff --git a/src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs b/src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs index c43f1e4f..235e168f 100644 --- a/src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs +++ b/src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs @@ -1,6 +1,7 @@ using Autodesk.Revit.DB; using Autodesk.Revit.UI; using IPA.Bcfier.Models.Bcf; +using IPA.Bcfier.Revit.OpenProject; namespace IPA.Bcfier.Revit.Services { @@ -29,6 +30,8 @@ public BcfViewpoint GenerateViewpoint() var topLeft = _uiDocument.GetOpenUIViews()[0].GetZoomCorners()[0]; var bottomRight = _uiDocument.GetOpenUIViews()[0].GetZoomCorners()[1]; + bcfViewpoint.ClippingPlanes = GetBcfClippingPlanes(_uiDocument); + if (_uiDocument.ActiveView.ViewType == ViewType.ThreeD) { //It's a 3d view @@ -185,5 +188,21 @@ public BcfViewpoint GenerateViewpoint() return null; } + + private static List GetBcfClippingPlanes(UIDocument uiDocument) + { + if (uiDocument.ActiveView is not View3D view3D) + { + return new List(); + } + + BoundingBoxXYZ sectionBox = view3D.GetSectionBox(); + XYZ transformedMin = sectionBox.Transform.OfPoint(sectionBox.Min); + XYZ transformedMax = sectionBox.Transform.OfPoint(sectionBox.Max); + Vector3 minCorner = transformedMin.ToVector3().ToMeters(); + Vector3 maxCorner = transformedMax.ToVector3().ToMeters(); + + return new AxisAlignedBoundingBox(minCorner, maxCorner).ToClippingPlanes(); + } } } diff --git a/src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs b/src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs index 6dbb3328..29a32655 100644 --- a/src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs +++ b/src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs @@ -1,6 +1,7 @@ using Autodesk.Revit.DB; using Autodesk.Revit.UI; using IPA.Bcfier.Models.Bcf; +using IPA.Bcfier.Revit.OpenProject; namespace IPA.Bcfier.Revit.Services { @@ -205,7 +206,7 @@ public RevitViewpointDisplayService(UIDocument uiDocument) using (var trans = new Transaction(_uiDocument.Document)) { - if (trans.Start("Apply BCF visibility and selection") == TransactionStatus.Started) + if (trans.Start("Apply BCF visibility and selection and section box") == TransactionStatus.Started) { if (elementsToHide.Any()) doc.ActiveView.HideElementsTemporary(elementsToHide); @@ -215,7 +216,13 @@ public RevitViewpointDisplayService(UIDocument uiDocument) if (elementsToSelect.Any()) _uiDocument.Selection.SetElementIds(elementsToSelect); + + if (_uiDocument.ActiveView is View3D view3d) + { + ApplyClippingPlanes(_uiDocument, view3d, bcfViewpoint); + } } + trans.Commit(); } @@ -254,5 +261,41 @@ public string GetName() { return "3D View"; } + + // Take from: + // https://github.com/opf/openproject-revit-add-in/blob/93e117ad10176f4fffa741116733a3ee113e9335/src/OpenProject.Revit/Entry/OpenViewpointEventHandler.cs#L212 + private const decimal _viewpointAngleThresholdRad = 0.087266462599716m; + + private void ApplyClippingPlanes(UIDocument uiDocument, View3D view, BcfViewpoint bcfViewpoint) + { + AxisAlignedBoundingBox boundingBox = GetViewpointClippingBox(bcfViewpoint); + + if (!boundingBox.Equals(AxisAlignedBoundingBox.Infinite)) + { + view.SetSectionBox(ToRevitSectionBox(boundingBox)); + view.IsSectionBoxActive = true; + } + } + + private AxisAlignedBoundingBox GetViewpointClippingBox(BcfViewpoint bcfViewpoint) + { + return bcfViewpoint.ClippingPlanes + .Select(p => p.ToAxisAlignedBoundingBox(_viewpointAngleThresholdRad)) + .Aggregate(AxisAlignedBoundingBox.Infinite, (current, nextBox) => current.MergeReduce(nextBox)); + } + + private static BoundingBoxXYZ ToRevitSectionBox(AxisAlignedBoundingBox box) + { + var min = new XYZ( + box.Min.X == decimal.MinValue ? double.MinValue : ((double)box.Min.X).ToInternalRevitUnit(), + box.Min.Y == decimal.MinValue ? double.MinValue : ((double)box.Min.Y).ToInternalRevitUnit(), + box.Min.Z == decimal.MinValue ? double.MinValue : ((double)box.Min.Z).ToInternalRevitUnit()); + var max = new XYZ( + box.Max.X == decimal.MaxValue ? double.MaxValue : ((double)box.Max.X).ToInternalRevitUnit(), + box.Max.Y == decimal.MaxValue ? double.MaxValue : ((double)box.Max.Y).ToInternalRevitUnit(), + box.Max.Z == decimal.MaxValue ? double.MaxValue : ((double)box.Max.Z).ToInternalRevitUnit()); + + return new BoundingBoxXYZ { Min = min, Max = max }; + } } } From 83e1e927100a464ee50575a7f3311929de68369d Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Sun, 7 Apr 2024 12:24:13 +0200 Subject: [PATCH 2/2] Update installer generation --- build/Build.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/build/Build.cs b/build/Build.cs index 66234358..c2267184 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -268,6 +268,7 @@ public static class FileVersionProvider File.Copy(pluginOutputDirectory / "Dangl.BCF.dll", installerDirectory / "Dangl.BCF.dll"); File.Copy(pluginOutputDirectory / "IPA.Bcfier.dll", installerDirectory/ "IPA.Bcfier.dll"); File.Copy(pluginOutputDirectory / "IPA.Bcfier.Revit.dll", installerDirectory/ "IPA.Bcfier.Revit.dll"); + File.Copy(pluginOutputDirectory / "DecimalEx.dll", installerDirectory/ "DecimalEx.dll"); File.Copy(pluginOutputDirectory / "IPA.Bcfier.Revit.addin", installerDirectory/ "IPA.Bcfier.Revit.addin"); InnoSetup($"/dAppVersion=\"{GitVersion.AssemblySemVer}\" {pluginOutputDirectory / "Installer.iss"}");