В сегодняшней статье рассмотрим очень важный класс — FilteredElementCollector. Этот класс позволяет нам взять некоторые (или все) элементы из документа, чтобы потом делать с ними определённые действия.
При работе в Dynamo его заменяют ноды "All Elements Of Category", "All Elements Of Type" и некоторые другие. В C# же их всех заменяет FilteredElementCollector.
Для начала внимательно изучим описание класса на revitapidocs.com. Чтобы начать работать с ним, нам надо создать его экземпляр с помощью одного из 3 конструкторов:
Первый принимает документ (сбор элементов по всему документу), второй — документ и ElementId вида (сбор элементов из документа на данном виде), третий — документ и коллекцию ElementId (фильтрация предварительно собранных элементов).
Как передать документ в нашу команду — вспомним из статьи о создании плагина. Далее мы можем создать экземпляр FilteredElementCollector:
В данном случае вместо явного указания типа я использую ключевое слово var. Это означает, что компилятор сам определит тип переменной. В данном случае я делаю так, потому что не знаю конечный тип переменной: будет ли это List, ICollection или что-то ещё, будет ли он состоять из Element, ElementId или я выполню приведение к конкретному типу (например, к Wall). Но иногда это ключевое слово используют для сокращения кода:
Строка var collector = new FilteredElementCollector(doc); гораздо короче, чем FilteredElementCollector collector = new FilteredElementCollector(doc);, при этом в обоих случаях тип переменной понятен (до вызова дополнительных методов).
Теперь рассмотрим методы этого класса из справки:
Данные методы помогают нам применять фильтры к коллектору, объединять отфильтрованные элементы с другими, переводить коллектор в список элементов или их Id, проходить по коллектору в цикле foreach. Рассмотрим наиболее важные из них.
1. public FilteredElementCollector OfCategory(
BuiltInCategory category )
Метод OfCategory принимает значение из enumeration (перечисление) BuiltInCategory и возвращает нам список всех элементов этой категории. То есть мы передаём ему не имя категории, а встроенное имя категории в OST_-формате (независимое от языка Revit). Как же его узнать?
1. Зная название категории на английском, перейти на страницу BuiltInCategory, нажать Ctrl-F, написать его и найти категорию:
2. Выделить элемент и посмотреть в Revit Lookup.
Далее синтаксис такой:
Передаём в метод название перечисления BuiltInCategory, затем через точку — элемент перечисления.
2. public FilteredElementCollector OfCategoryId(
ElementId categoryId )
Метод возвращает тоже самое — все элементы категории, но принимает Id категории (будьте внимательны, он всегда со знаком минус, выделить Категорию внутри Ревита нельзя).
Узнаём через Lookup:
Синтаксис такой:
Вообще, в конструктор для ElementId можно передать и BuiltInCategory — VisualStudio предложит вам такой вариант. И да, он сработает:
На самом деле, OST_Doors имеет внутри этот самый шифр -2000023. Как это устроено — читайте здесь.
3. public FilteredElementCollector OfClass(
Type type )
Собирает элементы класса. В чём разница c двумя предыдущими? Объясню на примерах:
- Есть категория Стены и класс Wall. Но первые 2 метода вернут нам и экземпляры, и типы стен в одном списке (тип элемента — тоже элемент), и, например, когда мы попытаемся во все элементы этого списка в параметр "Комментарии" написать значение, то получим ошибку, ведь у типа такого параметра нет.
- Класс может охватывать несколько категорий. MEPCurve — это трубы, лотки и воздуховоды. Так мы можем взять их все в одно действие.
- Элемент может не иметь категории (например, общий параметр — SharedParameterElement)
В общем, класс — это совсем не категория. Синтаксис метода такой:
Обязательно пишем ключевое слово typeof. Мы передаём не имя класса, а его тип (Type), и оно возвращает нам именно тип класса.
4-5. public FilteredElementCollector WhereElementIsElementType() и
public FilteredElementCollector WhereElementIsNotElementType()
2 метода, которые из нашего выбора возьмут либо только экземпляры, либо только типы. Например, мы можем написать так, в 2 вариантах:
То есть в первом случае мы берём все элементы класса WallType (типоразмеры стен), а во втором — все элементы категории "Стены", а потом из них берём только типоразмеры. Результат (в 99% случаях) будет одинаковый. Пишу 99%, потому что до конца не уверен, как поведут себя типы стен, импортированные из IFC.
6-7. public IList<Element> ToElements() и public IList<Element> ToElementIds()
Вызываются в конце. Превращают коллектор в список элементов или их Id. В большинстве случаев я пишу ToElements(). Потому что:
8. Cast<T>()
Этого метода нет в API, это метод расширения LINQ. Он приводит ("кастует") все элементы к другому типу.
Зачем нам это? Допустим, мы собрали все воздуховоды в список и хотим сделать с ними что-то (вписать габариты в параметр "Комментарии". Мы взяли все элементы класса Duct, и идём по ним в цикле. Но вот незадача — у элементов нет свойств воздуховодов. А в Lookup мы их видим:
Дело в том, что в цикле мы идём по коллекции, состоящей из Element. Это мы знаем, что в ней воздуховоды, а компилятор — не знает. И такой код не скомпилирует. То есть мы в цикле должны ввести для каждого элемента новую переменную типа Duct, привести её к нужному типу:
Duct duct = element as Duct;
И далее работать с ней. Проблемы особой нет, всего одна лишняя строчка.
Но можно решить элегантнее, и вызвать Cast<Duct>() после ToElements():
Данный код вернёт нам список воздуховодов (List<Duct>), и теперь мы можем для элементов списка использовать их свойства и методы. В конце я также вызвал ToList(), так как Cast возвращает IEnumerable, а не List.
А, ну и самое важное — тип, к которому вы приводите, нужно указывать в угловых скобках (вместо <Duct> из примера).
9. public FilteredElementCollector WherePasses(
ElementFilter filter )
Этот метод позволяет применить к коллекции любой другой фильтр из множества фильтров RevitAPI. Это тема для ещё одной большой статьи, поэтому подробно рассматривать её мы не будем. Если вам интересна эта тема, пишите в комментарии.
Для использования мы сначала создаём фильтр, а потом передаём его в коллектор:
А на этом всё. В следующей статье мы научимся создавать транзакции и изменять элементы.
Подписывайтесь на мой телеграм-канал о Revit API. До новых встреч!