• YouTube - Белый круг
  • Facebook - Белый круг
  • Instagram - Белый круг

+7 (495) 766-89-13

Россия, город Москва, проспект Мира д.3 стр.3, эт. 4 

м. Сухаревская (выход 3)

Структурное программирование

#python #revit #beginner


На примере практической задачи работы с Revit API рассмотрим одну из самых базовых методологий разработки ПО.


Если вкратце это о проектировании от общего к частному: мы продумываем и разбиваем задачу на ряд последовательных действий, формируем структуру программы из функций-заглушек, а потом последовательно их реализуем.


В качестве примера возьмем следующую задачу:

Добавление фильтра с заданными настройками на выбранные в диспетчере проекта виды.

Алгоритм предполагается простым:

  1. Выбрать виды в диспетчере проекта

  2. Найти фильтр с заданным именем

  3. Для каждого вида: - добавить фильтр https://www.revitapidocs.com/2020/eb21e133-38b0-5be7-8e81-5af8df37bb8c.htm - переопределить настройки отображения добавленного фильтра https://www.revitapidocs.com/2020/4f523352-a258-97dc-002f-cf328ca34566.htm


Для каждого этапа пишем функции заглушки:

  1. get_views()

  2. get_view_filter()

  3. set_filter_and_override()

Функция main() выглядит буквально как перевод алгоритма на английский, это не всегда возможно, но к этому надо стремиться.


В точке входа if name == 'main' готовимся ловить все возможные ошибки. В дальнейшем, в блоке except добавим логирование и сообщение об ошибке для пользователя.


На данный момент скрипт полностью работоспособен: он ничего не делает, но все функции вызываются в нужном порядке и ошибок при выполнении не возникает.

Так выглядят необходимые импорты при работе с PyScript, если вы используете другие решения, не забудьте их поправить.


Выбор видов


Список всех выбранных элементов доступен через ActiveUIDocument.

Теперь нам нужно убедится что элементы выбраны и среди них есть объекты класса View.

Cделаем это с помощью FilteredElementCollector.

При создании экземпляра коллектора в качестве второго аргумента можно передать список из ElementId (ICollection<ElementId>) из которых в дальнейшем он будет отфильтровывать нужные элементы.

Теперь необходимо подумать о крайних случаях: выбранных элементов может не быть вообще или среди них не будет видов:

Что не так:

  1. "Лесенка" из двух if-ов - вещь терпимая, в отличии от 3-х вложенных if-ов!

  2. В обоих крайних случаях функция вернет None (по умолчанию) значит здесь либо в main() нужно проверять views == None, и выводить сообщение об ошибке и прекращать выполнение скрипта. Возвращать из функции None - плохая практика. Добавлять в функцию код выводящий сообщения еще хуже. Это будет действием не отраженным в имени функции, но если исправить название на get_views_from_selection_or_show_warning - станет очевидно, что функция делает больше действий чем должна.

  3. В дальнейшем при модификации скрипта (либо при переиспользовании его частей для других задач) может понадобится выбирать элементы другим способ, а не только из выбранных вручную.

Решение:

Разбить функцию на две: get_selected_ids(), filter_views_from(ids)

И вместо того чтобы индивидуально обрабатывать ситуации, когда дальнейшее выполнение кода бессмысленно, мы будем выбрасывать(raise) свои собственные ошибки и ловить(except) их всех в одном единственном месте.


Вместо:

Так:

Зачем?

Каждая функция выполняет только одну задачу, понятную из ее названия, а основная часть скрипта отделена от обработки ошибок.

В этом маленьком примере польза от этого возможно не сильно очевидна. Но представьте более серьезную задачу, в которой сбой можем произойти внутри глубоко вложенного вызова функции или в сложных и разветвленных if-ax (которых не удалось избежать).

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

Все это лучше делать в отдельном месте, не засоряя основную логику.


Логи


logging - нечто вроде более продвинутой версии print.

Так как человеческий дебаггинг при работе в ревите нам не доступен, вместо

print("Не понимаю что происходит") можно использовать logging.debug("Не понимаю что происходит").

И вместо того чтобы оставлять кучу принтов в проблемных местах, удалять или поштучно комментить их, можно просто изменить уровень логирования: logging.basicConfig(level=logging.DEBUG), на .INFO и обратно.


logging - очень мощный модуль, который может из под коробки реализовать сценарии типа: выводить все в консоль, все что выше .info писать в в файл, а исключения уровня .exception отправлять на почту или веб-сервер.

Фильтр

Код мало чем отличается от предыдущих функций, итерируемся по коллектору фильтров и возвращаем первый с именем == filter_name.

Назначение фильтра

Пока просто добавляем, без переопределения графики.

На данном этапе уже можно проверить работоспособность всего скрипта.

Переопределение графики

Вариантов весьма много, особенно после добавлении поддержки двух штриховок.

Нас интересуют только следующие опции: возможность назначить сплошную заливку любого цвета, видимость, и полутон.

Для этого нужен объект OverrideGraphicSettings. Который передается вторым аргументом методу SetFilterOverrides(..).

Цвет

Для удобства можно использовать функцию конвертирующую HEX-строку в Color

Штриховка

Нам нужна только сплошная заливка, но штриховки - пользовательские элементы, и нет чего-то типа FILL_PATTERN, которым можно было бы воспользоватся, но есть свойство IsSolidFill, благодаря которому мы можем взять первую попавшуюся сплошную штриховку.

Все вместе

Все свойства мы можем передавать одним объектом:

Inputs

Добавим возможность пользовательского ввода:


Скрипт целиком

Ссылки

Просмотров: 642