Добавление графики в рабочее поле Revit. Введение в ExternalServices
Всем привет. Я немножко вдохновился зумерскими и альфа-мемами и решил выпустить наполненную мемами статью шестого июля
1. Введение
В составе Autodesk Revit есть библиотека AdWindows. Про неё немножко уже было в моём блоге (и даже не за моим авторством). Эта библиотека позволяет менять имена встроенных панелей, кнопок, лент, раскрашивать и переименовывать вкладки, вращать ленту инструментов Ревита. Но вот что с ней сделать очень сложно или даже невозможно — как-то модифицировать рабочее поле Revit. Оно представляет собой написанное на C++ окно, и наши C#-элементы просто всегда отображаются под ним.
Однако, встроенный Revit API позволяет нам немножко поиграться с тем, что есть на экране у пользователя (скорее всего, точки из рабочего поля Dynamo рендерятся в Revit именно так).
Давайте пойдём по порядку:
2. Class InCanvasControlData
Почитать о классе подробнее.
Базово это коллекция данных, которую использует TemporaryGraphicsManager для отображения временной графики. Эта графика не сохраняется в модели и не является её частью. То есть это просто та графика, которую мы, разработчики приложения, хотим вывести для пользователя.
Этот класс описывает графический элемент, который будет отрисован в рабочем пространстве Ревита. Он устанавливает довольно строгие ограничения:
- мы должны указать абсолютный путь на диске, dll-ресурсы не поддерживаются
- файл должен быть квадратным со стороной в 32, 64 или 128 пикселей
- файл должен быть в формате bmp
- если вы хотите прозрачный фон, нужно сделать файлу фон цвета RGB(0, 128, 128)
Про цвет фона я не проверял, у меня не получилось к моим bmp-добавить ровный фон. Да и в конце концов, я программист, а не дизайнер или фотошопер, так что думаю, вы меня простите.
Итак, в своём проекте я добавил 3 bmp-файла в качестве контента и добавил в команду код, который будет выводить случайным (для пользователя) способом одну из картинок в рабочее поле Revit:
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var document = commandData.Application.ActiveUIDocument.Document;
var view = document.ActiveView;
var manager = Autodesk.Revit.DB.TemporaryGraphicsManager.GetTemporaryGraphicsManager(document);
var divide = DateTime.Now.Second % 3;
var path = divide switch
{
1 => @"C:\Users\Sergei Nefyodov\source\repos\Revit-API-Blog\Resources\syomga.bmp",
2 => @"C:\Users\Sergei Nefyodov\source\repos\Revit-API-Blog\Resources\brdyshch.bmp",
_ => @"C:\Users\Sergei Nefyodov\source\repos\Revit-API-Blog\Resources\cheremsha.bmp",
};
var data = new InCanvasControlData(path);
manager.AddControl(data, view.Id);
return Result.Succeeded;
}
Получился вот такой результат:
В советском союзе в Ревите работал черемша
Что ж, уже неплохо, какую-то кастомную графику мы добавили, но пока мы не можем с ней никак взаимодействовать. Revit API позволяет нам делать и это — для этого есть интерфейс ITemporaryGraphicsHandler. Он наследует интерфейс IExternalServer и имеет в себе дополнительный метод OnClick() — тот самый метод, через который мы будем взаимодействовать с нашей кастомной графикой.
Давайте реализуем интерфейс, благо его методы не выглядят сложными. Я не буду особо запариваться с логикой на клик по картинке — просто картинка будет удаляться, и выводиться TaskDialog. В своём приложении вы уже сможете сделать всё, что захотите.
Например, выводить чат с ИИ по клику на Черемшу или Брдыща. Вот тут пример, как я делал чат с Claude для Revit (без Брдыща).
Код такой
public class CheremshaGraphicsHandler : ITemporaryGraphicsHandler
{
public Guid GetServerId()
{
return new Guid("DC1A13E6-1FC3-44D2-96FF-7CD8B5E7177B");
}
public ExternalServiceId GetServiceId()
{
return ExternalServices.BuiltInExternalServices.TemporaryGraphicsHandlerService;
}
public string GetName()
{
return "CheremshaGraphicsHandler";
}
public string GetVendorId()
{
return "USSR";
}
public string GetDescription()
{
return "CheremshaGraphicsHandler";
}
public void OnClick(TemporaryGraphicsCommandData data)
{
var divide = DateTime.Now.Second % 3;
if (divide == 0)
{
TaskDialog.Show("Черемша", "Окак. Тихо, не спеша");
}
else if (divide == 1)
{
TaskDialog.Show("Брдыщ", "Стекловата, шлакоблоки");
}
else if (divide == 2)
{
TaskDialog.Show("Сёмга", "Что у вас здесь происходит?");
}
var manager = Autodesk.Revit.DB.TemporaryGraphicsManager.GetTemporaryGraphicsManager(data.Document);
manager.RemoveControl(data.Index);
data.Dispose();
}
}
На что обратить внимание:
- ExternalServiceId возвращаем через встроенный класс ExternalServices.BuiltInExternalServices.TemporaryGraphicsHandlerService;
- Если мы удаляем InCanvasConrolData, не забывайте вызвать Dispose().
Отлично, сервис мы написали. Давайте зарегистрируем его через метод OnStartup при старте приложения:
public Result OnStartup(UIControlledApplication application)
{
var service = (MultiServerService)
ExternalServiceRegistry.GetService(ExternalServices.BuiltInExternalServices.TemporaryGraphicsHandlerService);
var handler = new CheremshaGraphicsHandler();
service.AddServer(handler);
service.SetActiveServers(new List<Guid>
{
handler.GetServerId()
});
return Result.Succeeded;
}
Что мы тут сделали:
- получили существующий сервис обработки временной графики
- создали наш экземпляр нашего кастомного сервера для её обработки
- назначили сервису наш сервер активным
Всё работает: при клике на картинку вызывается метод OnClick():

А на сегодня про добавление кастомной графики встроенными методами всё. Делитесь вашими идеями использования этой технологии с пользой в комментах и до новых встреч!
Код из статьи можно посмотреть здесь.
Мой телеграм-канал. Не забывайте подписаться!