Всем привет! Сегодня рассмотрим довольно сложную и интересную тему: добавление общих параметров, а также изменение назначенных им категорий. Тема сложная, потому что на некоторые категории я так и не смог программно добавить параметры. Впрочем, обо всё по порядку.
Исходные данные
- Файл на основе пустого шаблона
- Привязанный к Revit файл общих параметров с 3 тестовыми параметрами, первый из которых имеет тип "Денежная единица", чтобы изменяться по экземплярам групп
- Первый параметр накинут на категорию стены, остальные просто существуют в ФОПе
- Задачи:
Добавленный Параметр 1 накинуть на все категории и подкатегории, сделать изменяемым по группам. - Параметр 2 накинуть на все категории, по экземпляру.
- Параметр 3 накинуть на все категории, по типу.
Порядок решения задачи
Сначала найдём всё, что нам нужно, в Revit Lookup и на https://www.revitapidocs.com/2023/. Затем я покажу код, который выполняет задачу, и подробно распишу его. Ну а потом посмотрим результат.
Поиск решения с помощью Revit Lookup
Настоятельно советую скачать самую новую версию Revit Lookup. Помимо обновлённого дизайна, она более удобная, практичная и понятная. Как и где качать.
С помощью Revit Lookup мы можем решить только первую часть задачи: накинуть категории на существующий параметр. Для второй части чуть сложнее: Revit Lookup не может показать то, чего ещё нет в документе.
Итак, данные обо всех параметрах и накинутых на них категорий отличаются от документа к документу. Поэтому выбираем Revit Lookup — Snoop Document (или выбираем любой элемент, у него есть свойство Document, и переходим по нему). У Документа есть свойство ParameterBindings. В общем-то, там всё и хранится:
Мы видим объект BindingMap. У меня добавлен один параметр , поэтому он и содержит только один элемент с тем самым именем параметра.
Рассмотрим класс BindingMap. В этой статье нас интересуют методы Insert (добавляет параметр) и ReInsert (обновляет параметр).
Для вызова этого метода нам нужны экземпляры класса 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.
В чём суть:
- Нам нужно создать сет из категорий, на которые мы хотим накинуть параметры
- На основании этого сета создать соответствующий 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();
А далее рассмотрим метод поиска:
DefinitionFile содержит свойство Groups. Это группы параметров. Внутри каждой есть свойство Definitions, которое представляет собой набор ExternalDefinition. Далее нам нужно просто выбрать тот, что нам нужен.
Примечание: делать такой код — неправильно. Лучше проверять не Name, а GUID. Имя может совпасть или поменяться, GUID же уникален.
Я дважды вызову этот метод в коде, и получу Definition для второго и третьего параметров по их имени.
Итоговый код
Ссылка на гитхаб будет в конце статьи. Ранее указанные методы там тоже будут
Опишу тут то, о чём не говорил ранее:
- Созданный на строке 35 сет категорий я применю во всех 3 параметрах. Это необязательно, если у вас другие задачи, то создавайте отдельные сеты под каждый параметр.
- В строке 45 я вызываю
parameter.GetDefinition().SetAllowVaryBetweenGroups(doc, true);
Это нужно, чтобы параметр изменялся по экземплярам групп. Отмечу, что метод ReInsert почему-то сбивал у меня эту настройку, поэтому я вызвал SetAllowVaryBetweenGroups после него.
- Добавление категорий обязательно оборачиваем в транзакцию — это модификация документа
Результат работы плагина:
Итак, 3 параметра, с заявленными настройками, появились в проекте.
Также на скрине вы можете видеть проблему, о которой я говорил в самом начале статьи:
- Категория "Короба" в Ревите как бы одна, а в окошке "Свойства параметров" их две. Одна для коробов с соединительными деталями, другая для коробов без них. А накинуть параметр программно можно только на первую.
- Почему не активна галочка "Опоры"? А вы найдите её в Revit Lookup (Document—Settings—Categories). У неё AllowsBoundParameters == false. Руками при этом привязать к ней параметры вполне возможно.
Если вы знаете рабочее решение, как пофиксить это, пишите в комментарии.
Возможные ошибки
Плагин может не сработать, если:
- В Revit не выбран файл общих параметров.
- В ФОП нет того параметра, который написан в коде.
Если у вас не сработал по другой причине, и вы разобрались почему, и считаете важным сообщить другим, пишите мне в телеграм-канал. Ладно, это я для разнообразия, шучу, пишите здесь в комментарии (но и там тоже можете).
Итоговый код в моём репозитории на GitHub
А на этом всё. Пробуйте воспроизвести это самостоятельно с поправкой на свои нужды, делитесь мыслями и результатами с коллегами под этой статьёй, и подписывайтесь на мой телеграм-канал. Всем успехов в изучении Revit API!