Geometry Revit API。如何获得多个元素的边界框?
我需要找到许多元素的大纲(>100000项)。目标元素来自Geometry Revit API。如何获得多个元素的边界框?,geometry,revit-api,revit,Geometry,Revit Api,Revit,我需要找到许多元素的大纲(>100000项)。目标元素来自FilteredElementCollector。像往常一样,我在寻找最快的方法 目前,我试图迭代所有元素以获得其BoudingBox.Min和BoudingBox.Max,并找出minX,minY,minZ,maxX,maxY,maxZ。它工作得相当准确,但需要太多时间 上述问题是更大问题的一部分。 我需要从链接模型中找到风管、管道和其他基于曲线的图元与常规模型中的墙、天花板、柱等的所有交点,然后在交点处放置洞口 我尝试使用Eleme
FilteredElementCollector
。像往常一样,我在寻找最快的方法
目前,我试图迭代所有元素以获得其BoudingBox.Min
和BoudingBox.Max
,并找出minX
,minY
,minZ
,maxX
,maxY
,maxZ
。它工作得相当准确,但需要太多时间
上述问题是更大问题的一部分。 我需要从链接模型中找到风管、管道和其他基于曲线的图元与常规模型中的墙、天花板、柱等的所有交点,然后在交点处放置洞口 我尝试使用
ElementIntersectElement
filter和IntersectSolidAndCurve
方法组合来查找元素内部的曲线部分
首先,使用element intersectelement
我试图减少一个集合,以便进一步使用IntersectSolidAndCurve
IntersectSolidAndCurve
采用两个参数:solid和curve,并且必须在两个嵌套的循环中工作,一个在另一个循环中。因此,在我的案例972'000'000操作中,需要54000个墙(过滤后)和18000个管道
操作数为10^5时,算法显示可接受的时间。
我决定通过将搜索区域按级别划分来减少元素的数量。这对高层建筑很有效,但对延伸的低层结构仍然不好。我决定用长度来划分建筑,但我没有找到一种方法来为几个元素(整个建筑)寻找边界
我似乎走错了路。使用revit api instrument是否有正确的方法原则上,您所描述的是正确的方法,也是唯一的方法。但是,优化代码的可能性可能很多。建筑编码器提供了各种可能有帮助的实用功能。比如说去,。还有更多的人。在那里搜索“边界框”。我相信它们也可以针对您的情况进一步优化。例如,您可以从所有单个元素的边界框
Max
值中提取所有X
坐标,并使用通用Max
函数在一次调用中确定它们的最大值,而不是逐个进行比较。发现优化可能性并分析其对性能的影响。请分享您的最终结果,供其他人学习。谢谢大家! 要找到边界,我们可以利用二进制搜索的思想
与经典的二进制搜索算法不同的是没有数组,我们应该找到两个数字而不是一个
几何空间中的元素可以表示为XYZ点的三维排序数组
Revit api提供了优秀的快速过滤器
:BoundingBoxIntersectsFilter
,它以大纲为例
那么,让我们定义一个区域,它包含我们想要找到其边界的所有元素。对于我的情况,例如500米,并为初始轮廓创建min
和max
点
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);
getbounders
方法返回两个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;
}
感谢Jeremy在这个问题上的建议和投入。我在上面公布了我的最终结果,并对性能和准确性进行了一些研究。我的答案中的代码处理时间为3-5秒/10万个元素,在大多数情况下都是准确的。但是,在某些情况下,
BoundingBoxIntersectsFilter
会在项目未穿过大纲时对其进行过滤。如果族中存在不可见的几何图形,则会发生这种情况。我还没有找到其他可能的原因。无论如何,还需要做更多的测试。非常感谢您的欣赏和分享您有趣的代码。使用内置Revit过滤机制肯定比在.NET中、Revit内存之外实现的任何功能都要快得多。然而,我还不明白你如何利用这一点来实现上述目标。我以为你需要所有元素的集合边界框。然而,您似乎有一个500米的输入变量,并且正在检查它是否包含所有元素。你能解释一下这个算法的确切用法,以及准确的输入和输出数据吗?非常感谢。杰里米,你完全正确。三维空间中的最小点和最大点被输入到方法中,这样所有元素都在基于这些点构建的轮廓内,然后我们沿着x y z找到极值点。输出为2个轮廓点。我更新了我的问题,使之更加精确。我更新了问题,但主题有了一点变化。我需要开始一个新的问题还是在这里继续讨论?谢谢你的解释和建议。我会抓住你的公式,并编辑它的博客文章,如果这是适合你的。我会给你寄一张汇票来检查是否正确。然后,如果你愿意,我们可以在博客上找到完整编辑的问题和答案。我不认为需要新的问题。当然,Jeremy,如果这段代码有用的话,你可以使用它。如果它能帮助别人,我会很高兴的。如果revit api中提供了这种方法,那就太好了
public static class PointExt
{
public static double Projection(this XYZ vector, XYZ other) =>
vector.DotProduct(other) / other.GetLength();
}