Найти тему

Управление общими параметрами с помощью Revit API

Оглавление

Всем привет! Сегодня рассмотрим довольно сложную и интересную тему: добавление общих параметров, а также изменение назначенных им категорий. Тема сложная, потому что на некоторые категории я так и не смог программно добавить параметры. Впрочем, обо всё по порядку.

Исходные данные

  • Файл на основе пустого шаблона
  • Привязанный к Revit файл общих параметров с 3 тестовыми параметрами, первый из которых имеет тип "Денежная единица", чтобы изменяться по экземплярам групп
  • Первый параметр накинут на категорию стены, остальные просто существуют в ФОПе
Параметры, которые будем использовать, добавлены в активный ФОП.
Параметры, которые будем использовать, добавлены в активный ФОП.
Первый параметр накинут только на стены и не может изменяться по экземплярам групп, ещё 2 не накинуты
Первый параметр накинут только на стены и не может изменяться по экземплярам групп, ещё 2 не накинуты
  • Задачи:
    Добавленный Параметр 1 накинуть на все категории и подкатегории, сделать изменяемым по группам.
  • Параметр 2 накинуть на все категории, по экземпляру.
  • Параметр 3 накинуть на все категории, по типу.

Порядок решения задачи

Сначала найдём всё, что нам нужно, в Revit Lookup и на https://www.revitapidocs.com/2023/. Затем я покажу код, который выполняет задачу, и подробно распишу его. Ну а потом посмотрим результат.

Поиск решения с помощью Revit Lookup

Настоятельно советую скачать самую новую версию Revit Lookup. Помимо обновлённого дизайна, она более удобная, практичная и понятная. Как и где качать.

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

Итак, данные обо всех параметрах и накинутых на них категорий отличаются от документа к документу. Поэтому выбираем Revit Lookup — Snoop Document (или выбираем любой элемент, у него есть свойство Document, и переходим по нему). У Документа есть свойство ParameterBindings. В общем-то, там всё и хранится:

-3

Мы видим объект BindingMap. У меня добавлен один параметр , поэтому он и содержит только один элемент с тем самым именем параметра.

Рассмотрим класс BindingMap. В этой статье нас интересуют методы Insert (добавляет параметр) и ReInsert (обновляет параметр).

-4

Для вызова этого метода нам нужны экземпляры класса Definition и Binding. Давайте разбираться по порядку.

Если вы уже имели опыт работы с параметрами через API, то вы, наверное, заметили, что у параметра есть свойство Definition. Да, с существующим параметром всё просто: находим его и берём это свойство. А как взять Definition от ещё не созданного параметра?
Вспомним статью про
наследование и посмотрим, что это за Definition. У этого класса есть 2 наследника: InternalDefinition и ExternalDefinition. При этом в описании к свойству Definition класса Parameter указано, что оно всегда возвращает InternalDefinition. А в описании класса ExternalDefinition сказано:

The ExternalDefinition object can be created by a definition Group object from a shared parameters file.

То есть на самом деле всё просто. InternalDefinition — это внутреннее определение существующего внутри Revit параметра (любого). А ExternalDefinition — это внешнее определение параметра (только общего, существующего пока только в виде строки в ФОП для данного документа).

PS1. Если слово Definition было написано слишком часто, пишите в комментарии, постараюсь исправится

PS2. На самом деле, у этих классов может быть более подробное предназначение и более сильная разница, которую я могу не знать. Если знаете, в чём дело — тоже пишите в комментарии.

Итак, что делать с существующим параметром, понятно. Как взять параметр из ФОПа, рассмотрим чуть позже.

Далее идёт класс Binding. У него сложная структура наследования. Нас интересуют классы InstanceBinding и TypeBinding, и их конструкторы, принимающие CategorySet.

В чём суть:

  1. Нам нужно создать сет из категорий, на которые мы хотим накинуть параметры
  2. На основании этого сета создать соответствующий Binding

Давайте рассмотри код, который создаёт сет из всех категорий:

Потом мы вызовем этот метод из основного кода команды
Потом мы вызовем этот метод из основного кода команды

Создаём пустой сет.

Получаем список всех категорий документа по свойствам Settings.Categories — строка 73 (попробуйте сами найти их в Lookup).

Проверяем, позволяет ли категория привязывать параметры (AllowsBoundParameters == true) — строка 75. Если да, добавляем сет. Если нет, идём к следующей.

Далее проверяем подкатегории. Если подкатегория позволяет привязку параметров, добавляем её в сет.

Почему так странно проверяем категории?

Дело в том, что цикл foreach может проходить только с объектами, наследующими интерфейс IEnumerable (перечисляемое). Как работает цикл foreach: создается объект Enumerator (перечислитель), он вызывает метод MoveNext(), пока ему есть куда двигаться, а затем возвращает объект по свойству Current. В свойстве SubCategories использован необобщённый Enumerator, поэтому он возвращает Object вместо Category. Поэтому я пишу строку if (value is Category subCategory), которая проверяет тип и объявляет переменную

Подробнее читайте здесь

Добавление в проект параметра из файла общих параметров

Откроем файл общих параметров:

Application application = commandData.Application.Application;
var parametersFile = application.OpenSharedParameterFile();

А далее рассмотрим метод поиска:

-6

DefinitionFile содержит свойство Groups. Это группы параметров. Внутри каждой есть свойство Definitions, которое представляет собой набор ExternalDefinition. Далее нам нужно просто выбрать тот, что нам нужен.

Примечание: делать такой код — неправильно. Лучше проверять не Name, а GUID. Имя может совпасть или поменяться, GUID же уникален.

Я дважды вызову этот метод в коде, и получу Definition для второго и третьего параметров по их имени.

Итоговый код

Ссылка на гитхаб будет в конце статьи. Ранее указанные методы там тоже будут

-7

Опишу тут то, о чём не говорил ранее:

  • В строке 33 я нахожу все общие параметры с помощью FilteredElementCollector.
  • Созданный на строке 35 сет категорий я применю во всех 3 параметрах. Это необязательно, если у вас другие задачи, то создавайте отдельные сеты под каждый параметр.
  • В строке 45 я вызываю
parameter.GetDefinition().SetAllowVaryBetweenGroups(doc, true);

Это нужно, чтобы параметр изменялся по экземплярам групп. Отмечу, что метод ReInsert почему-то сбивал у меня эту настройку, поэтому я вызвал SetAllowVaryBetweenGroups после него.

  • Добавление категорий обязательно оборачиваем в транзакцию — это модификация документа

Результат работы плагина:

-8

Итак, 3 параметра, с заявленными настройками, появились в проекте.
Также на скрине вы можете видеть проблему, о которой я говорил в самом начале статьи:

  • Категория "Короба" в Ревите как бы одна, а в окошке "Свойства параметров" их две. Одна для коробов с соединительными деталями, другая для коробов без них. А накинуть параметр программно можно только на первую.
  • Почему не активна галочка "Опоры"? А вы найдите её в Revit Lookup (DocumentSettingsCategories). У неё AllowsBoundParameters == false. Руками при этом привязать к ней параметры вполне возможно.

Если вы знаете рабочее решение, как пофиксить это, пишите в комментарии.

Возможные ошибки

Плагин может не сработать, если:

  • В Revit не выбран файл общих параметров.
  • В ФОП нет того параметра, который написан в коде.
  • В CategorySet попала категория с AllowsBoundParameters == false.

Если у вас не сработал по другой причине, и вы разобрались почему, и считаете важным сообщить другим, пишите мне в телеграм-канал. Ладно, это я для разнообразия, шучу, пишите здесь в комментарии (но и там тоже можете).

Итоговый код в моём репозитории на GitHub

А на этом всё. Пробуйте воспроизвести это самостоятельно с поправкой на свои нужды, делитесь мыслями и результатами с коллегами под этой статьёй, и подписывайтесь на мой телеграм-канал. Всем успехов в изучении Revit API!

-9