Найти в Дзене

Создание приложений для Revit с большим количеством команд

Оглавление

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

Эта статья о создании приложения, в котором будут условно 5 и более кнопок. Я не рассматриваю здесь архитектурные вопросы самих плагинов (IExternalCommand), только создание общей архитектуры (IExternalApplication).

Шаг 1.

Самый важный шаг — выбрать хороший шаблон проекта. Чтобы вам не пришлось каждый раз вручную писать addin-файлы, жонглировать зависимостями, думать, как опять подключить Community.Toolkit.Mvvm или добавить сборку. Я возьму шаблон от Романа Карповича Nice3Point. Он имеет следующий преимущества:

  • Встроенная поддержка нескольких версий Revit.
  • Встроенная поддержка сборки msi локально и на GitHub.
  • Встроенная поддержка WPF и автоматическое создание ViewModel.
  • Удобный класс Сontext для доступа к текущему документу в любом месте.

Так как на основе этого шага мы будем делать все дальнейшие действия, распишу его подробно:

  1. Читаем Wiki по шаблону
  2. Открываем Visual Studio и нажимаем "Продолжить без кода":

4. Запускаем терминал:

-2

5. Пишем там "dotnet new install Nice3point.Revit.Templates" и жмём Enter

Вот и у меня версия обновилась
Вот и у меня версия обновилась

Шаг 2. Создание решения

Шаблоны установлены, можно создавать проект:

1. Создаём проект на шаблоне Revit Addin Solution:

-4

2. Настраиваем проект

Не забудьте выбрать подходящее имя
Не забудьте выбрать подходящее имя
-6

Шаг 3. Создание проекта для решения

Мы будем создавать приложения по принципу: один модуль — один проект. В большинстве случаев будет так: одна команда — один модуль, но, вообще, это не обязательно.

Подробнее о проектах и решениях.

В C# мы имеем Решение: файл .sln. В состав Решения входят Проекты .csproj. Проекты могут ссылаться друг на друга, но только в одну сторону

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

Поэтому я предлагаю такое решение проблемы нескольких команд в одном приложении:

1. Создаём проект для приложения Application, который знает всё о всех кнопках — других проектах.

2. Создаём проекты-кнопки.

3. Создаём общий проект CommonProject для вынесения общей логики в одно место для уменьшения дублирования кода.

4. При необходимости создаём новые общие проекты с другими задачами.

На диаграмме зависимостей это будет выглядеть так:

-7

Вернёмся к нашему решению. Оно сейчас выглядит так:

-8

Создадим проект для IExternalApplication. Жмём на решение правой кнопкой и выбираем Добавить — Создать проект. Выбираем Revit Addin Application:

Не забудьте выбрать папку source для проекта
Не забудьте выбрать папку source для проекта
Пока не будем добавлять serilog и IoC (DI), это тема для другой статьи
Пока не будем добавлять serilog и IoC (DI), это тема для другой статьи

У нас получилось приложение:

-11

У него есть addin-файл и уже создана панель. Отлично, минус одна задача для нас.

Шаг 4. Создание остальных проектов

Я создам 3 проекта: 2 для команд, и один проект для вынесения общей для всех проектов логики. Для этого я использую шаблон Revit Addin Module. Он не создаёт addin-файла и лишних папок:

Не забываем source
Не забываем source
-13

Для общего проекта создадим проект без пользовательского интерфейса. Для команд использую модальное окно:

-14

Вот такой результат получился:

-15

Шаг 5. Настройка связей между проектами

Сделаем так:

1. В проекте CommonProject определим класс RevitShell со статическим методом SayHello, который будет выводить сообщение "Hello world!"

2. В проекте Application создадим кнопки для команд из других проектов

3. Внутри команд определим внутреннюю логику команды, а также выведем сообщение из проекта CommonProject.

RevitShell в CommonProject теперь выглядит так:

-16

Здесь я использую одну из фишек шаблона: обращаюсь к текущему документу из удобного места через класс Сontext, не задумываясь о том, как передать этот документ через ExternalCommandData.

Вот так мы добавляем ссылки на команды и их проекты в класс Application:

-17

И точно так же создаём ссылку на RevitShell в проектах командах:

-18
Важно: ваши проекты не обязаны ссылаться на общий проект, может быть, у вас там и не будет никакой логики, подходящей для вынесения в общий проект, и вы обойдётесь без него, или конкретный проект обойдётся без ссылки на CommonProject. Но я рекомендую создать его заранее, чтобы в случае чего легко можно было его подключить и не дублировать код. И самое важное: CommonProject не должен ссылаться на проекты-команды.

Итак, я добавил всё нужную мне логику, и все связи между проектами. Теперь могу запустить приложение.

Шаг 6. Запуск

Шаблон от Nice3Point поддерживает удобную сборку и отладку проекта. Выберем активную конфигурацию решения Application, конфигурацию Debug R23 и запустим проект в режиме отладки. Ревит откроется автоматически:

-19

Результат:

-20

В папке %username\AppData\Roaming\Autodesk\Revit\Addins\2023\Application появились созданные нами dll и все нужные зависимости. Addin-файл также скопировался:

-21

Шаг 7 (бонусный) Получение msi

Я уже писал про развёртывание сборки, которое мы делаем сами от начала до конца. Шаблоны от Романа поддерживают сборку "из коробки". В Visual Studio откроем терминал и напишем "nuke CreateInstaller" и нажмём Enter.

Если ранее не пользовались nuke, то надо его установить, написав в терминале команду "dotnet tool install Nuke.GlobalTool --global", а затем запускать установку через "nuke CreateInstaller".

-22

Сначала вылезет ошибка, потому что в файле Build.Configuration у нас указано название решение, не совпадающие с название проекта Application. Меняем:

Было
Было
Стало
Стало

Перезапустим команду "nuke CreateInstaller" в терминале. В папке решения появилась папка output с msi-сборкой.

-25

Заключение

Итак, у меня получилось создать решение, в которое легко можно добавить новые проекты для других команд, вынести общую логику в отдельный проект. В такой конфигурации очень сложно запутаться в папках и файлах — если один проект будет сложный с кучей всего кроме Views и ViewModels, то всё это останется в рамках одного проекта. Однако, данное решение не претендует на то, чтобы быть истиной в последней инстанции: если у вас есть опыт разработки приложения для Revit с большим числом кнопок, то делитесь им в комментариях, особенно, если считаете, что данную задачу можно решить другим способом.

На этом на сегодня всё. Итоговый код — на Github. Проект, скорее всего, будет "обрастать мясом", но общая концепция — останется. Не забывайте ставить звёзды на мои репозитории и подписываться на мой телеграм-канал о Revit API. До новых встреч!

-26