【问题标题】:Revit API. How can I get bounding box for several elements?Revit API。如何获得多个元素的边界框?
【发布时间】:2020-11-14 22:21:32
【问题描述】:

我需要为许多元素(>100'000 个项目)找到一个大纲。目标元素来自FilteredElementCollector。像往常一样,我正在寻找最快的方法。

现在我尝试遍历所有元素以获取其BoudingBox.MinBoudingBox.Max 并找出minXminYminZmaxXmaxYmaxZ。它工作得相当准确,但需要太多时间。


上面描述的问题是更大问题的一部分。 我需要在通用模型中找到与墙、天花板、柱等的连接模型中的管道、管道和其他基于曲线的元素的所有交叉点,然后在交叉点放置开口。

我尝试结合使用ElementIntersectElement 过滤器和 IntersectSolidAndCurve 方法在元素内查找曲线的一部分。

首先使用ElementIntersectElement,我尝试减少集合以供进一步使用IntersectSolidAndCurveIntersectSolidAndCurve 采用两个参数:solid 和 curve,并且必须在其他循环中的两个嵌套的一个中工作。因此,在我的情况下,972'000'000 次操作需要 54000 个墙壁(过滤后)和 18000 个管道。

运算次数为 10 ^ 5 时,算法显示出可接受的时间。 我决定通过按级别划分搜索区域来减少元素的数量。这适用于高层建筑,但对于扩展的低结构仍然不利。我决定按长度划分建筑物,但没有找到一种方法可以找到多个元素(整个建筑物)的边界。

我好像走错了路。有没有正确的方法来使用 revit api 工具

【问题讨论】:

  • 我更新了问题,但主题发生了一点变化。我需要在这里提出一个新问题还是继续讨论?
  • 感谢您的解释和建议。我将按原样获取您的配方并对其进行编辑以撰写博客文章,如果您可以的话。我会给你发一份草稿来检查是否正确。然后,如果您愿意,我们可以指向博客文章以获取完整编辑的问题和答案。我认为不需要提出新问题。
  • 当然,Jeremy,如果有帮助,您可以使用这段代码。如果它帮助别人,我会很高兴。如果开箱即用的 revit api 中提供了这种方法,那就太好了

标签: geometry revit-api revit


【解决方案1】:

原则上,您所描述的是正确的方法和唯一的方法。但是,优化您的代码可能有很多可能性。 Building Coder 提供了各种可能有所帮助的实用功能。例如,determine the bounding box of an entire familyThe Building Coder samples Util module 中的更多内容。在那里搜索“边界框”。我相信它们也可以针对您的情况进一步优化。例如,您可以从所有单个元素的边界框Max 值中提取所有X 坐标,并使用通用Max 函数在一次调用中确定它们的最大值,而不是一一比较它们. Benchmark your code 发现优化可能性并分析它们对性能的影响。请分享您的最终结果,以供其他人学习。谢谢!

【讨论】:

  • 感谢 Jeremy 对此问题的建议和意见。我在上面发布了我的最终结果,并对性能和准确性进行了一些研究。我的答案中的代码在 3-5 秒/100'000 个元素内处理,并且在大多数情况下工作准确。但是,在某些情况下,BoundingBoxIntersectsFilter 会在项目未穿过 Outline 时对其进行过滤。如果族中存在不可见的几何图形,则会发生这种情况。还有其他可能的原因我还没有找到。无论如何都需要做更多的测试。
  • 非常感谢您的赞赏和分享您有趣的代码。使用内置的 Revit 过滤机制肯定会比在 Revit 内存之外的 .NET 中实现的任何东西都要快得多。但是,我还不明白如何使用它来实现上述目标。我认为您需要所有元素的集体边界框。但是,您似乎有一个 500 米的输入变量,并且正在检查它是否包含所有元素。你能解释一下这个算法的确切用途,以及确切的输入和输出数据吗?谢谢!
  • 杰里米,你说得对。 3d 空间中的最小和最大点被馈送到方法的输入,因此所有元素都在基于这些点的轮廓内,然后我们找到沿 x y z 的极值点。输出为 2 个轮廓点。我更新了我的问题以更准确
【解决方案2】:

为了找到边界,我们可以利用二分搜索思想。

与经典的二分查找算法不同的是没有数组,我们应该找到两个数字而不是一个。

几何空间中的元素可以表示为 XYZ 点的 3 维排序数组。

Revit api 提供了出色的 Quick Filter: BoundingBoxIntersectsFilter,它采用了 Outline 的实例

所以,让我们定义一个区域,其中包含我们想要找到边界的所有元素。对于我的情况,例如 500 米,并为初始轮廓创建 minmax

    double b = 500000 / 304.8;
    XYZ min = new XYZ(-b, -b, -b);
    XYZ max = new XYZ(b, b, b);

下面是一个方向的实现,但是,您可以通过调用上一次迭代的结果并将其提供给输入来轻松地将其用于三个方向

     double precision = 10e-6 / 304.8;
     var bb = new BinaryUpperLowerBoundsSearch(doc, precision);

     XYZ[] rx = bb.GetBoundaries(min, max, elems, BinaryUpperLowerBoundsSearch.Direction.X);
     rx = bb.GetBoundaries(rx[0], rx[1], elems, BinaryUpperLowerBoundsSearch.Direction.Y);
     rx = bb.GetBoundaries(rx[0], rx[1], elems, BinaryUpperLowerBoundsSearch.Direction.Z);

GetBoundaries方法返回两个XYZ点:lower和upper,仅在目标方向变化,其他两个维度保持不变

    public class BinaryUpperLowerBoundsSearch
    {
        private Document doc;

        private double tolerance;
        private XYZ min;
        private XYZ max;
        private XYZ direction;

        public BinaryUpperLowerBoundsSearch(Document document, double precision)
        {
            doc = document;
            this.tolerance = precision;
        }

        public enum Direction
        {
            X,
            Y,
            Z
        }

        /// <summary>
        /// Searches for an area that completely includes all elements within a given precision.
        /// The minimum and maximum points are used for the initial assessment. 
        /// The outline must contain all elements.
        /// </summary>
        /// <param name="minPoint">The minimum point of the BoundBox used for the first approximation.</param>
        /// <param name="maxPoint">The maximum point of the BoundBox used for the first approximation.</param>
        /// <param name="elements">Set of elements</param>
        /// <param name="axe">The direction along which the boundaries will be searched</param>
        /// <returns>Returns two points: first is the lower bound, second is the upper bound</returns>
        public XYZ[] GetBoundaries(XYZ minPoint, XYZ maxPoint, ICollection<ElementId> elements, Direction axe)
        {
            // Since Outline is not derived from an Element class there 
            // is no possibility to apply transformation, so
            // we have use as a possible directions only three vectors of basis 
            switch (axe)
            {
                case Direction.X:
                    direction = XYZ.BasisX;
                    break;
                case Direction.Y:
                    direction = XYZ.BasisY;
                    break;
                case Direction.Z:
                    direction = XYZ.BasisZ;
                    break;
                default:
                    break;
            }

            // Get the lower and upper bounds as a projection on a direction vector
            // Projection is an extention method
            double lowerBound = minPoint.Projection(direction);
            double upperBound = maxPoint.Projection(direction);

            // Set the boundary points in the plane perpendicular to the direction vector. 
            // These points are needed to create BoundingBoxIntersectsFilter when IsContainsElements calls.
            min = minPoint - lowerBound * direction;
            max = maxPoint - upperBound * direction;


            double[] res = UpperLower(lowerBound, upperBound, elements);
            return new XYZ[2]
            {
                res[0] * direction + min,
                res[1] * direction + max,
            };
        }

        /// <summary>
        /// Check if there are any elements contains in the segment [lower, upper]
        /// </summary>
        /// <returns>True if any elements are in the segment</returns>
        private ICollection<ElementId> IsContainsElements(double lower, double upper, ICollection<ElementId> ids)
        {
            var outline = new Outline(min + direction * lower, max + direction * upper);
            return new FilteredElementCollector(doc, ids)
                .WhereElementIsNotElementType()
                .WherePasses(new BoundingBoxIntersectsFilter(outline))
                .ToElementIds();
        }


        private double[] UpperLower(double lower, double upper, ICollection<ElementId> ids)
        {
            // Get the Midpoint for segment mid = lower + 0.5 * (upper - lower)
            var mid = Midpoint(lower, upper);

            // Сheck if the first segment contains elements 
            ICollection<ElementId> idsFirst = IsContainsElements(lower, mid, ids);
            bool first = idsFirst.Any();

            // Сheck if the second segment contains elements 
            ICollection<ElementId> idsSecond = IsContainsElements(mid, upper, ids);
            bool second = idsSecond.Any();

            // If elements are in both segments 
            // then the first segment contains the lower border 
            // and the second contains the upper
            // ---------**|***--------
            if (first && second)
            {
                return new double[2]
                {
                    Lower(lower, mid, idsFirst),
                    Upper(mid, upper, idsSecond),
                };
            }

            // If elements are only in the first segment it contains both borders. 
            // We recursively call the method UpperLower until 
            // the lower border turn out in the first segment and 
            // the upper border is in the second
            // ---*****---|-----------
            else if (first && !second)
                return UpperLower(lower, mid, idsFirst);

            // Do the same with the second segment
            // -----------|---*****---
            else if (!first && second)
                return UpperLower(mid, upper, idsSecond);

            // Elements are out of the segment
            // ** -----------|----------- **
            else
                throw new ArgumentException("Segment is not contains elements. Try to make initial boundaries wider", "lower, upper");
        }

        /// <summary>
        /// Search the lower boundary of a segment containing elements
        /// </summary>
        /// <returns>Lower boundary</returns>
        private double Lower(double lower, double upper, ICollection<ElementId> ids)
        {
            // If the boundaries are within tolerance return lower bound
            if (IsInTolerance(lower, upper))
                return lower;

            // Get the Midpoint for segment mid = lower + 0.5 * (upper - lower)
            var mid = Midpoint(lower, upper);

            // Сheck if the segment contains elements 
            ICollection<ElementId> idsFirst = IsContainsElements(lower, mid, ids);
            bool first = idsFirst.Any();

            // ---*****---|-----------
            if (first)
                return Lower(lower, mid, idsFirst);
            // -----------|-----***---
            else
                return Lower(mid, upper, ids);

        }

        /// <summary>
        /// Search the upper boundary of a segment containing elements
        /// </summary>
        /// <returns>Upper boundary</returns>
        private double Upper(double lower, double upper, ICollection<ElementId> ids)
        {
            // If the boundaries are within tolerance return upper bound
            if (IsInTolerance(lower, upper))
                return upper;

            // Get the Midpoint for segment mid = lower + 0.5 * (upper - lower)
            var mid = Midpoint(lower, upper);

            // Сheck if the segment contains elements 
            ICollection<ElementId> idsSecond = IsContainsElements(mid, upper, ids);
            bool second = idsSecond.Any();

            // -----------|----*****--
            if (second)
                return Upper(mid, upper, idsSecond);
            // ---*****---|-----------
            else
                return Upper(lower, mid, ids);
        }

        private double Midpoint(double lower, double upper) => lower + 0.5 * (upper - lower);
        private bool IsInTolerance(double lower, double upper) => upper - lower <= tolerance;

    }

投影是向量的一种扩展方法,用于确定一个向量对另一个向量的投影长度

    public static class PointExt
    {
        public static double Projection(this XYZ vector, XYZ other) =>
            vector.DotProduct(other) / other.GetLength();
    }

【讨论】:

    猜你喜欢
    • 2019-05-14
    • 1970-01-01
    • 2016-10-27
    • 1970-01-01
    • 2020-07-25
    • 2021-08-14
    • 2018-12-04
    • 2013-09-17
    • 1970-01-01
    相关资源
    最近更新 更多