Найти в Дзене

Поиск элементов в Revit API с помощью фильтров. Наследование в C#

Оглавление

Недавно я получил первый комментарий к своей статье, с просьбой подробнее разобрать метод WherePasses(ElementFilter filter). По такому случаю я решил разобрать эту тему, а также, казалось бы, несвязанную с ней тему наследования в C#. В конце рассмотрим, как это всё можно применить на практике.

Наследование

Предположим, что мы — авторы Revit API. Начинаем с азов: написали класс Element, добавили в него свойства, пусть будут Name, Id, Document, Category, методы добавили (get_Parameter()). Пишем дальше. Доходим до класса Wall. И тут загвоздка: каждая стена — Элемент, но не каждый элемент — Стена. Ну мы люди простые: свойства и методы скопировали из класса Element. Ещё несколько классов написали, которые являются по сути элементам: Group, Duct, FamilyInstance. И в них тоже скопировали код. А потом обнаружили ошибку в логике передачи ElementId. И в Element исправили, а в Wall забыли. А программисты сидят и не понимают, что происходит, и почему Id то один, то другой. Мы же будем страдать, переписывая логику вычисления Id во множестве классов.

Как вы понимаете, ничего такого не происходит благодаря наследованию. Наследование позволяет в сохранить в производных классах (например, Wall, Duct) функциональность базовых классов (Element) без дублирования кода. Хорошо, но как нам это использовать? Рассмотрим на примере:

Класс Duct непосредственно наследуется от класса MEPCurve — это мы видим в записи public class Duct : MEPCurve. Соответственно, MEPCurve — наследник HostObject, он — Element, а Element наследуется от system.Object — это базовый класс для абсолютно всех элементов в C#.

То есть на самом деле у экземпляров класса Duct есть свойства и методы всех вышеназванных классов, а не только 3 уникальных для воздуховода. Чтобы увидеть их все, мы можем перейти в "Duct Members", там будут перечислены все методы, а также указано, от какого класса они унаследованы.

Когда это важно учитывать? Допустим, мы создали воздуховод статическим методом Duct.Create. Он вернул нам экземпляр класса Duct. И далее мы можем использовать его как обычный Element — передавать в любые методы, требующие на вход Element.

Класс ElementFilter

Зайдём в класс ElementFilter и посмотрим, какие у него есть производные классы:

-2

Провалимся по очереди в ElementQuickFilter и в ElementSlowFilter:

Наследники ElementQuickFilter
Наследники ElementQuickFilter
Наследники ElementSlowFilter
Наследники ElementSlowFilter

То есть в метод WherePasses(), требующий ElementFilter, мы можем передать любой из этих фильтров, в зависимости от того, что нам нужно.

В чём же разница между Quick и Slow фильтрами? Очевидно, что первый быстрый, второй — медленный. Но в принципе разница описана прямо в описании класса ElementQuickFilter — быстрые фильтры оперируют со связанными с элементами записями базы данных — классом ElementRecord (Если мы сравним его методы и типы быстрых фильтров, то увидим, что они очень похожи).

Важный момент, который следует учитывать — всегда перед запуском медленного фильтра следует отфильтровать коллекцию быстрым фильтром. Условно, сначала фильтруем по классу/категории, потом — по значению параметра. Рассмотрим это на примере: напишем код, который ищет пересечения элементов, в 2 вариантах, а заодно научимся замерять время выполнения и работе с разными вариантами ElementFilter

Поиск пересечений через Revit API

Итак, что сейчас сделаем: создадим модель, наполним её стенами и воздуховодами, так, чтобы воздуховоды пересекали стены, а затем найдём все пары стена-воздуховод с пересечениями. Сделаем это 2 способами: сначала через ElementIntersectsElementFilter (медленный фильтр), а затем применим быстрый фильтр BoundingBoxIntersectsFilter, а после него — опять же, ElementIntersectsElementFilter. Для обоих случаев замерим время выполнения.

Для начала я создал небольшую модель со стенами и воздуховодами, и потом раскопировал их, чтобы пересечений было много: вот отчёт из встроенной в Revit проверки:

8085 пересечений
8085 пересечений

А потом написал вот такой код:

-6

Разберём по порядку:

Класс Stopwatch позволяет замерить время выполнения. Я создал его экземпляр на 25 строке, запустил на 26, остановил на 40 и вывел результат (свойство TotalSecond класса TimeSpan (свойства Elapsed) на 41-42.

Строки 27-29 и 33 — взятие элементов FilterdElementCollector'ом.

На 32 строке мы создали экземпляр класса ElementIntersectsElementFilter для каждой стены, а затем взяли все воздуховоды, которые проходят этот фильтр (пересекаются с ней)

В строке 33 я специально вынес фильтр по классу после фильтра по пересечению элемента, чтобы увеличить время выполнения в демонстрационных целях. Если поменять их местами, время выполнения будет ещё быстрее.

TaskDialog на строке 42 выведет нам время работы, а на строке 43 — список всех пар пересечений.

Итак, запустим плагин:

12,29 секунд
12,29 секунд
и 8085 пересечений
и 8085 пересечений

Итак, 12,3 секунды. Отлично. Теперь чуть-чуть модифицируем наш код:

-9

На строке 31 мы взяли BoundingBox стены, затем создали из него Outline, который является аргументом для создания BoundingBoxIntersectsFilter — быстрого фильтра. Затем мы применяем сначала этот фильтр, а потом — фильтр по пересечению с элементом (медленный). Запускаем:

8,5 секунд — прогресс на 3,8 секунд
8,5 секунд — прогресс на 3,8 секунд

То есть в данном случае применение предварительной фильтрации даёт нам выигрыш в 30%. Если же у нас по какой-то причине будет вложенный цикл с повторной фильтрацией, результат может получится ещё быстрее.

Результат по числу пересечений — такой же:

-11

Подведём итог: в метод WherePasses() класса FilteredElementCollector мы можем передавать любой ElementFilter — любой из наследников этого класса. Фильтры бывают быстрые и медленные; сначала следует использовать быстрые, затем медленные — это приводит к выигрышу по времени.

А на этом всё. В следующей статье подробно рассмотрим класс Parameter и работу с параметрами.

Подписывайтесь на мой телеграм-канал о Revit API. До новых встреч!

-12