diff --git a/src/IPA.Bcfier.Navisworks/OpenProject/AxisAlignedBoundingBox.cs b/src/IPA.Bcfier.Navisworks/OpenProject/AxisAlignedBoundingBox.cs
new file mode 100644
index 0000000..b9dc3fa
--- /dev/null
+++ b/src/IPA.Bcfier.Navisworks/OpenProject/AxisAlignedBoundingBox.cs
@@ -0,0 +1,67 @@
+// This was mostly taken from:
+// https://github.com/opf/openproject-revit-add-in
+
+namespace IPA.Bcfier.Navisworks.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.Navisworks/OpenProject/BcfExtensions.cs b/src/IPA.Bcfier.Navisworks/OpenProject/BcfExtensions.cs
new file mode 100644
index 0000000..cafea66
--- /dev/null
+++ b/src/IPA.Bcfier.Navisworks/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.Navisworks.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.Navisworks/OpenProject/ClippingPlaneUtils.cs b/src/IPA.Bcfier.Navisworks/OpenProject/ClippingPlaneUtils.cs
new file mode 100644
index 0000000..a03d52f
--- /dev/null
+++ b/src/IPA.Bcfier.Navisworks/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.Navisworks.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.Navisworks/OpenProject/Extensions.cs b/src/IPA.Bcfier.Navisworks/OpenProject/Extensions.cs
new file mode 100644
index 0000000..ef27481
--- /dev/null
+++ b/src/IPA.Bcfier.Navisworks/OpenProject/Extensions.cs
@@ -0,0 +1,45 @@
+// This was mostly taken from:
+// https://github.com/opf/openproject-revit-add-in
+
+namespace IPA.Bcfier.Navisworks.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.Navisworks/OpenProject/Position.cs b/src/IPA.Bcfier.Navisworks/OpenProject/Position.cs
new file mode 100644
index 0000000..27ccf6b
--- /dev/null
+++ b/src/IPA.Bcfier.Navisworks/OpenProject/Position.cs
@@ -0,0 +1,51 @@
+// This was mostly taken from:
+// https://github.com/opf/openproject-revit-add-in
+
+namespace IPA.Bcfier.Navisworks.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.Navisworks/OpenProject/Vector3.cs b/src/IPA.Bcfier.Navisworks/OpenProject/Vector3.cs
new file mode 100644
index 0000000..882e81b
--- /dev/null
+++ b/src/IPA.Bcfier.Navisworks/OpenProject/Vector3.cs
@@ -0,0 +1,92 @@
+// This was mostly taken from:
+// https://github.com/opf/openproject-revit-add-in
+
+namespace IPA.Bcfier.Navisworks.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.Navisworks/Services/NavisworksViewpointCreationService.cs b/src/IPA.Bcfier.Navisworks/Services/NavisworksViewpointCreationService.cs
index 4371901..62c6a45 100644
--- a/src/IPA.Bcfier.Navisworks/Services/NavisworksViewpointCreationService.cs
+++ b/src/IPA.Bcfier.Navisworks/Services/NavisworksViewpointCreationService.cs
@@ -2,6 +2,7 @@
using Autodesk.Navisworks.Api.Clash;
using IPA.Bcfier.Models.Bcf;
using IPA.Bcfier.Models.Clashes;
+using IPA.Bcfier.Navisworks.OpenProject;
using IPA.Bcfier.Navisworks.Utilities;
namespace IPA.Bcfier.Navisworks.Services
@@ -101,7 +102,12 @@ private BcfViewpoint GetViewpointFromNavisworksViewpoint(Viewpoint viewpoint)
};
}
- var selectedIfcGuids = _doc.CurrentSelection.SelectedItems.Select(selectedItem => selectedItem.InstanceGuid.ToIfcGuid()).ToList();
+ var elementBoundingBoxes = new List();
+ var selectedIfcGuids = _doc.CurrentSelection.SelectedItems.Select(selectedItem =>
+ {
+ elementBoundingBoxes.Add(selectedItem.BoundingBox());
+ return selectedItem.InstanceGuid.ToIfcGuid();
+ }).ToList();
if (selectedIfcGuids.Any())
{
v.ViewpointComponents = new BcfViewpointComponents
@@ -114,6 +120,25 @@ private BcfViewpoint GetViewpointFromNavisworksViewpoint(Viewpoint viewpoint)
};
}
+ if (elementBoundingBoxes.Any())
+ {
+ // We need to construct a common bounding box for all selected elements
+ var minX = elementBoundingBoxes.Min(b => b.Min.X);
+ var minY = elementBoundingBoxes.Min(b => b.Min.Y);
+ var minZ = elementBoundingBoxes.Min(b => b.Min.Z);
+ var maxX = elementBoundingBoxes.Max(b => b.Max.X);
+ var maxY = elementBoundingBoxes.Max(b => b.Max.Y);
+ var maxZ = elementBoundingBoxes.Max(b => b.Max.Z);
+
+ var commonBoundingBox = new BoundingBox3D(new Point3D(maxZ.FromInternal(), maxY.FromInternal(), minZ.FromInternal()),
+ new Point3D(minX.FromInternal(), minY.FromInternal(), maxZ.FromInternal()));
+
+ var clippingPlanes = TransformBoundingBoxToClippingPlanes(commonBoundingBox);
+
+ v.ClippingPlanes ??= new List();
+ v.ClippingPlanes.AddRange(clippingPlanes);
+ }
+
#if NAVISWORKS_2023 || NAVISWORKS_2022 || NAVISWORKS_2021
var navisworksSnapshot = _doc.GenerateImage(ImageGenerationStyle.Scene, 1920, 1080);
@@ -370,5 +395,81 @@ private static Rotation3D MultiplyRotation3D(Rotation3D r2, Rotation3D r1)
rot.Normalize();
return rot;
}
+
+ private List TransformBoundingBoxToClippingPlanes(BoundingBox3D clippingBox)
+ {
+ Vector3 center = new Vector3(clippingBox.Center.X.ToDecimal(), clippingBox.Center.Y.ToDecimal(), clippingBox.Center.Z.ToDecimal());
+
+ var planes = new List();
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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 }
+ });
+
+ 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;
+ }
}
}
+