Добавление графики в рабочее поле 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();
    }
}

На что обратить внимание:

Отлично, сервис мы написали. Давайте зарегистрируем его через метод 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(): Результат работы

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

Код из статьи можно посмотреть здесь.

Мой телеграм-канал. Не забывайте подписаться!