API BIM-системы Renga

    Всем привет! В этой статье я расскажу об API BIM-системы Renga. О самой системе можно почитать тут, здесь же можно запросить версию для некоммерческого использования. Если вкратце, то Renga это трехмерная система автоматизированного проектирования в архитектуре и строительстве. В ней проектировщик/архитектор/конструктор работает с информационной моделью здания, получает ассоциативные чертежи, спецификации, в общем, создает проект.



    Зачем нужно API CAD-системы


    Сначала, как водится, немного водички.

    Разработка расширений для CAD систем довольно распространена, поскольку в любом проектировании существуют различные направления, разделы и стандарты оформления проектной документации, которые требуют разной узкоспециализированной функциональности. Кроме того существуют задачи интеграции с программами расчета, визуализации, документооборота и многими другими. Выход — создание подключаемых модулей, расширяющих функциональность системы.

    Пример простого расширения


    Разберем основные шаги для создания расширения. Вам понадобится VisualStudio и уверенность, что вы знаете C++ (сейчас у Renga C++ API, но в перспективе у нас переход на COM, будем поддерживать .Net расширения).

    Итак, по шагам:

    1. Скачиваем SDK отсюда, распаковываем в удобное место.
    2. Создаем простую dll с настройками по умолчанию, назовем MyPlugin.
    3. В дополнительных папках включений и дополнительных папках lib добавляем путь к RengaSDK, во включаемые библиотеки добавляем RengaApi.lib и RengaBase.lib.
    4. Теперь напишем минимальную реализацию. По сути C++ расширение к Renga — обычная dll библиотека с экспортной функцией, которая возвращает указатель на интерфейс IPlugin. Поэтому все что нам нужно сделать:

      а)Создать класс, унаследованный от этого интерфейса. В классе реализуем оба метода интерфейса start и stop, эти методы будут вызваны после загрузки и перед выгрузкой соответственно. Реализацию пока оставим пустой.

      b) Реализовать экспортную функцию, возвращающую указатель на интерфейс IPlugin. Для этого можно воспользоваться макросом EXPORT_PLUGIN.
    5. Создадим файл описания расширения с именем MyPlugin.rnedesc с таким текстом:

      <RengaPlugin>
          <Name>MyPlugin</Name>
          <Version>1.0</Version>
          <Vendor>Vendor name</Vendor>
          <Copyright>Copyright text</Copyright>
          <RequiredAPIVersion>1.2</RequiredAPIVersion>
          <PluginFilename>MyPlugin.dll</PluginFilename>
      </RengaPlugin>
    6. Для того, чтобы расширение появилось в Renga в папке установки Renga в папке Plugins, создаем папку MyPlugin, в которую кладем нашу dll и файл описания.

    Если все сделано правильно, то при запуске Renga в диалоге настроек в разделе “Расширения” появится наше расширение, которое ничего не делает.

    Давайте заставим его вычислять количество кирпича, необходимого для возведения стен в здании. Норму расхода возьмем такую — 400 штук на кубический метр с учетом швов.

    1. Для начала создадим кнопку в основной панели приложения. Сделаем это прямо в методе initialize

    bool MyPlugin::initialize(const wchar_t * pluginPath)
    {
      auto countBricksAction = rengaapi::UIControls::createAction();
      countBricksAction.setToolTip(rengabase::String(L"Рассчитать количество кирпичей в стенах"));
    
      auto primaryPanelExtension = rengaapi::UIControls::createToolPanelExtension();
      primaryPanelExtension.addToolButton(countBricksAction);
      
      return true;
    }

    2. Дальше реализуем обработчик нажатия кнопки, создадим класс CountBricksHandler, наследника от интерфейса rengaapi::IInvokable. Создадим его в классе MyPlugin и зададим как обработчика кнопки.

    
    //CountBricksHandler.h
    #pragma once
    #include <RengaAPI/IInvokable.h>
    
    class CountBricksHandler : public rengaapi::IInvokable
    {
    public:
      void invoke();
    };
    
    //CountBricksHandler.cpp
    #include "stdafx.h"
    #include "CountBricksHandler.h"
    
    void CountBricksHandler::invoke()
    {}
    

    3. Объявим поле m_countBricksHandler в приватной секции класса MyPlugin, зададим в качестве обработчика для countBricksAction.

    
    auto countBricksAction = rengaapi::UIControls::createAction();
      countBricksAction.setToolTip(rengabase::String(L"Count bricks in walls"));
      countBricksAction.setTriggerHandler(&m_countBricksHandler);
    

    4. Теперь осталось реализовать расчет количества кирпичей в стенах. Создадим простой класс — калькулятор, который будет считать объем материала “Кирпич”, используемого в стенах, и выдавать количество в зависимости от переданной нормы расхода. Вызовем расчет в методе Invoke обработчика кнопки.

    
    //BricksCounter.h
    
    #pragma once
    
    namespace rengaapi
    {
      class Wall;
    }
    
    class BricksCounter
    {
    public:
      BricksCounter(int consumptionRatePerM3);
      int calculateBricks();
    
    private:
      double calculateBricksVolume();
      double calculateBricksVolumeInSingleWall(rengaapi::Wall* pWall);
      int calculateBricksCountInVolume(double bricksVolume);
    
    private:
      int m_consumptionRatePerM3;
    };
    

    
    //BricksCounter.cpp
    #include "stdafx.h"
    #include "BricksCounter.h"
    
    #include <RengaAPI/Project.h>
    #include <RengaAPI/ModelObjectTypes.h>
    #include <RengaAPI/Wall.h>
    #include <RengaAPI/Materials.h>
    
    const wchar_t* c_briksMaterialName = L"Кирпич";
    
    BricksCounter::BricksCounter(int consumptionRatePerM3)
      : m_consumptionRatePerM3(consumptionRatePerM3)
    {
    }
    
    int BricksCounter::calculateBricks()
    {
      double bricksVolume = calculateBricksVolume();
      int bricksNumber = calculateBricksInVolume(bricksVolume);
      return bricksNumber;
    }
    
    double BricksCounter::calculateBricksVolume()
    {
      double result = 0.0;
    
      assert(rengaapi::Project::hasProject());
      auto allObjects = rengaapi::Project::model().objects();
      for (auto objectIt = allObjects.begin(); objectIt != allObjects.end(); ++objectIt)
      {
        if ((*objectIt)->type() == rengaapi::ModelObjectTypes::WallType)
        {
          rengaapi::Wall* pWall = dynamic_cast<rengaapi::Wall*>(*objectIt);
          assert(pWall != nullptr);
          result += calculateBricksVolumeInSingleWall(pWall);
        }
      }
    
      return result;
    }
    
    double BricksCounter::calculateBricksVolumeInSingleWall(rengaapi::Wall * pWall)
    {
      auto materialId = pWall->material();
      rengaapi::LayeredMaterial wallMaterial;
      rengaapi::Materials::layeredMaterial(materialId, wallMaterial);
      auto materialLayers = wallMaterial.layers();
    
      auto layerQuantityCollection = pWall->quantities().materialQuantities();
      
      double bricksVolume = 0.0;
      for (size_t i = 0; i < materialLayers.size(); ++i)
      {
        if (materialLayers.get(i).material().name_() == rengabase::String(c_briksMaterialName))
        {
          auto oVolumeMeasure = layerQuantityCollection.get(i).netVolume();
          if (oVolumeMeasure.hasValue())
          {
            bricksVolume += oVolumeMeasure.getValue()->inMeters3();
          }
        }
      }
      return bricksVolume;
    }
    
    int BricksCounter::calculateBricksInVolume(double bricksVolume)
    {
      return static_cast<int>(bricksVolume * m_consumptionRatePerM3);
    }
    

    
    // CountBricksHandler.cpp
    
    #include "stdafx.h"
    #include "CountBricksHandler.h"
    #include "BricksCounter.h"
    
    #include <RengaAPI/Project.h>
    #include <RengaAPI/Message.h>
    
    const int c_bricksConsumptionRatePerM3 = 400;
    
    void CountBricksHandler::invoke()
    {
      if (!rengaapi::Project::hasProject())
      {
        rengaapi::Message::showMessageBox(rengaapi::Message::Info,
          rengabase::String(L"Сообщение MyPlugin"),
          rengabase::String(L"Проект отсутствует. Невозможно подсчитать поличество кирпича."));
      }
      else
      {
        BricksCounter counter(c_bricksConsumptionRatePerM3);
        int bricksCount = counter.calculateBricks();
        
        std::wstring message = std::wstring(L"Количество кирпича в стенах, шт: ") + std::to_wstring(bricksCount);
        rengaapi::Message::showMessageBox(rengaapi::Message::Info,
          rengabase::String(L"Сообщение MyPlugin"),
          rengabase::String(message.data()));
      }
    }
    

    Полный код расширения находится тут



    Еще про расширения Renga


    В принципе все для начала работы с API Renga есть в SDK (http://rengabim.com/sdk/), там же есть и примеры на все основные возможности.

    Также в качестве примеров мы разработали расширение для просмотра модели в виде иерархической структуры (дерева) и расширение для фильтрации объектов модели по различным критериям. Эти расширения доступны как в собранном виде, так и виде исходного кода, можно использовать как пример или дорабатывать по вкусу. Кроме того, сторонними разработчиками созданы расширения для 3D рендеринга и для экспорта в 3D PDF. Эти и новые расширения ищите здесь.

    Что позволяет API


    Сейчас API Renga позволяет:

    • Экспортировать 3D представление объектов в виде полигональной сетки (mesh-представление), разделенное на группы, чтобы можно было, к примеру, отличить фасад стены от торца.
    • Добавлять элементы управления в пользовательский интерфейс Renga (ну и реагировать на них, само собой).
    • Получать параметры и расчетные характеристики трехмерных объектов модели (объемы, площади, размеры и т.п.).
    • Создавать пользовательские свойства, назначать их объектам модели, менять значение.
    • Управлять видимостью и выделять объекты модели.

    На сегодня это все, будем рады ответить на ваши вопросы. Пишите под Renga, генерируйте идеи, развивайтесь вместе с нами!

    Евгений Тян, ведущий программист, Renga Software.
    АСКОН 146,51
    Крупнейший российский разработчик инженерного ПО
    Поделиться публикацией
    Комментарии 4
    • +1
      Итак, буду первым комментатором))
      Сегодня узнал, что сделали поддержку C# и мне стало это интересно. Ознакомился со справкой (с вступлением), скачал SDK, попробовал примеры и сразу возникли вопросы. Писать на почту не стал, чтобы еще кто-то мог подключиться к общению или почитать ответ.
      Итак, вопросы и замечания:
      1. В справке (http://help.rengabim.com/api/index.html#how-to-install-plugin) написано:
      If some errors prevent the plugin from loading successfully, you can check the application log for details. The log is stored in %localappdata%/Ascon/Renga Architecture/AecApp.log.
      В этом пути нет Ascon! Поправьте
      2. Как мне программно узнать, где находится Ренга? Нашёл в реестре HKEY_LOCAL_MACHINE\SOFTWARE\ASCON, но там только информация о лицензии.
      3. К вопросу о лицензии — допустим, я хочу попробовать написать плагин для Ренги — я должен успеть за 60 дней? Или купить? Нет, спасибо))
      4. Самый главный вопрос из-за которого я и решил написать — вопрос по установке плагинов. Плагины читаются из папки [Renga Installation directory]/Plugins/ и это ОЧЕНЬ ПЛОХАЯ ИДЕЯ! По умолчанию Ренга ставится в Program Files и в большинстве случаев у пользователей в проектных организациях НЕТ ТУДА ДОСТУПА!
      • +1
        Здравствуйте, спасибо за комментарии!
        1. Вы правы, путь поменялся, поправим.
        2. Напишите на почту, вышлем ключ реестра с путем, инсталлятор прописывает путь. Наверное стоит добавить в справку API, подумаем.
        3. Специальной лицензии для разработчиков нет. Помимо триала есть бесплатная лицензия для некоммерческого использования, можете получить на сайте.
        4. Разработчики плагинов, как правило, делают свой инсталлятор, требующий повышения прав. Renga при установке тоже требует повышения прав. Если пользователь проектной организации установил Renga, то, скорее всего, установит и плагин. Если же это делал администратор, то тогда, видимо, это его ответственность.
        • 0
          Добавьте ключ реестра в справку — это будет правильно.
          Бесплатная лицензия действует 60 дней. Или вы про что-то другое?

          Я являюсь разработчиком плагина ModPlus. Можете погуглить. Так вот — основная идея, к которой я очень долго шёл, заключается в том, что мой плагин по сути является платформой и позволяет подключать нужные пользователю функции. Поэтому, первым делом, я оцениваю возможность написания плагинов под Ренгу с точки зрения своих меркантильных целей.
          В моём случае имеется всего-лишь один плагин, который подключается к конечному продукту (Автокад, Ревит) и который уже сам загружает все остальное. Можно ли такое реализовать в Ренге? Что-то я пока в этом не уверен.
          Конечно, я могу и по-другому сделать — копировать конкретные плагины в папку Plugins (как у вас и задумано), но возникают другие проблемы — зависимые библиотеки. Например, у меня есть своя библиотека ModPlusAPI.dll которую «потребляют» абсолютно все части плагина. Копировать её каждый раз в папку Ренги — плохая идея. И таких библиотек несколько. Плюс, такой подход может вызвать другие проблемы, связанные с конфликтом библиотек разных версий. А если под Ренгу начнут писать плагины, то такие проблемы явно будут. Тот же самый Newtonsoft.Json обновляется постоянно…
          В связи с этим возник еще такой вопрос — в файле, описывающем плагин (не знаю как правильно по-русски его назвать) есть элемент PluginFilename. Вопрос — если он будет указывать на файл из другой папки, то Ренга будет грузить этот файл? Могу я в папку с плагином положить только .rndesc?
          • 0
            Бесплатная лицензия выдается без ограничения времени для некоммерческого использования.
            Вариант с вынесенным плагином не рассматривали пока, есть обходные пути, но они все с ограничениями. Вроде задания относительного пути в rndesc файле. У нас есть пример крупного продукта/плагина, который имеет отдельный инсталлятор, регистрирует приложение на машине. Его плагин состоит из библиотеки, которая общается с этим приложением посредством COM.

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

      Самое читаемое