Revit API之元素检索优化

简介

虽然计算机的性能越来越强大,二次开发者常常不再需要为软件的效率而伤脑经,然而想要做出商业化的应用,仍然需要我们精益求精。相信大家都十分关心如何提高程序运行的效率。本篇推文将介绍在Revit API环境中进行元素检索优化的具体实践。

在Revit 2012之后的API环境中,从当前的Document的属性列表中无法再取到任何元素(Element),元素的取得都依赖于元素检索器(FilteredElementCollector)。这样一来在一个应用中将可能需要大量的使用元素检索器来获取特定元素,如何有效率的使用检索器将对二次开发应用的效率产生显著的影响。事实上Jeremy也有一篇博文专门来阐述如何优化元素检索器,本文很多内容来自于Jeremy的文章,但是也加入了很多作者在自身开发经历中的一些自己的解读。下面给出原文链接,有兴趣阅读原文的同学可以自行查阅:TheBuildingCoder


原则

对于元素检索器,应当注意以下几点原则:

  1. 尽力避免依赖于名称的检索器,尽可能的选择使用BuiltInParameters。
  2. 如果非要使用名称的话,使用Parameter Filter的效率将远超LINQ。
  3. 将一个filtered element collector 转换为一个.Net的collection(如IEnumerable)的过程常常是可以避免的,这样做会把一个collector的所有成员拷贝到.Net collection中,在大集合中这样做对效率的影响尤其明显。

Anti patten

下边是一个Anti patten,也就是不好的实践,我们应当避免这样使用元素检索器:

1
2
3
4
5
6
IEnumerable<Element> targetElems =  
from element in collector
where element.Name.Equals( targetName )
select element;

IList<Element> elems = targetElems.ToList();

正确的做法

在Revit SDK samples中经常看到有例子将collector转成iterator然后循环遍历,这样同样不是一个好的实践,事实上是直接用foreach循环将达到非常好的效率:

1
2
3
4
5
6
7
8
FilteredElementCollector collector
= new FilteredElementCollector( doc )
.OfCategory( BuiltInCategory.OST_Doors )
.OfClass( typeof( FamilySymbol ) );
foreach( FamilySymbol symbol in collector )
{ Family family = symbol.Family;
// Process reference to doors family
}

不但代码更加简洁,而且没有了collector的转换,性能上也有提升。

ElementParameterFilter

然而在现实中我们经常地需要使用名称来过滤元素,例如Revit的族名称就取决于文件名,这常常让我们面对一个尴尬的现状:上面的代码用不上了。。当确实需要使用名称来匹配检索的时候,作者本人自己做过测试发现,使用LINQ来实现速度会比使用foreach循环快好多倍,更是甩iterator几条街,这是因为LINQ语句不需要像foreach那样循环从集合中第一个元素开始遍历。然而我接下来要讲的却并不是用LINQ来实现元素检索,否则Jeremy也不能被称之为大神了!我也是使用LINQ很久以后才发现在面对这种情况的时候Revit API提供了另一个强大的接口ElementParameterFilter。下面这个方法是此类问题的具体实现(作者对代码进行了修改):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Element GetFirstElementOfTypeWithBipString(
Document doc,
Type type,
BuiltInParameter bip,
string name)
{
FilteredElementCollector a
= new FilteredElementCollector(doc).OfClass(type);
ParameterValueProvider provider
= new ParameterValueProvider(
new ElementId( bip ) );
FilterStringRuleEvaluator evaluator
= new FilterStringEquals();
FilterRule rule = new FilterStringRule(
provider, evaluator, name, true );
ElementParameterFilter filter
= new ElementParameterFilter( rule );
return a.WherePasses( filter ).FirstElement();
}

根据Jeremy对不同的检索方法做了测试
Retrieve specific named level:

Percentage Seconds Calls Process
0.00% 0.00 1000 Empty method *
0.19% 0.11 1000 Collector with no name check *
9.19% 5.46 1000 Parameter filter
22.53% 13.37 1000 Explicit
22.57% 13.40 1000 Anonymous named
22.73% 13.49 1000 Anonymous
22.73% 13.49 1000 Linq
100.00% 59.35 1 TOTAL TIME

根据结果我们可以看到,进行一千次调用所耗时间的一个对比,不使用名称进行检索的时候,检索效率已经降低到一个惊人的程度,这也是为什么Revit新的API中不再把以前的一些常用的Element作为Document公开属性的原因,这些元素可以被低代价地检索到,所用时间几乎可以忽略不计,而节省的内存资源和降低的软件复杂度却是可观的。

LINQ

虽然ElementParameterFilter的效率很高,然而它只也只能通过元素的Parameter来进行过滤,很多情况下我们可能无法通过Parameter来获取其名称,例如想要通过名称获取Element的过程中,我们能够在Level的参数列表中找到Name参数,但是在FamilySymbol中找到同样的参数,这个时候使用LINQ反而能够获得比其他方法更高的效率。下面是一段通过名称获取Element的LINQ代码:

1
2
3
4
5
6
7
8
9
10
11
12
public static Element GetElementByName(
Document doc,
Type type,
string name)
{
var element =
new FilteredElementCollector(doc)
.OfClass(type)
.FirstOrDefault(
elem => elem.Name == name);
return element;
}

简单的做个对比:
对比

结语

好了,本期的文章到此结束,希望你的编码过程同样也充满乐趣!

欢迎转载,请注明出处!欢迎关注微信公众号GoldenBlue_Dev
GoldenBlue_Dev
有好的意见或者建议也可联系本人微信号:frobo
froboo

文章目录
  1. 1. 简介
  2. 2. 原则
  3. 3. Anti patten
  4. 4. 正确的做法
  5. 5. ElementParameterFilter
  6. 6. LINQ
  7. 7. 结语