Как стать автором
Обновить
74.46
АСКОН
Разработчик инженерного ПО и ИТ-интегратор

Взаимодействие C3D Solver с приложением на примере моделирования сборки

Уровень сложностиСредний
Время на прочтение12 мин
Количество просмотров606

С3D Solver – это инструмент для разработчиков, работающих с 2D и 3D-моделированием. Он позволяет создавать параметрические сборки из твёрдых тел и эскизы, накладывая на них связи (ограничения). Мы остановимся непосредственно на трёхмерном решателе, чтобы на его примере ответить на возникающие у разработчиков приложений вопросы, которые и послужили толчком к написанию данной статьи. Например, расскажем о значении синхронизации представлений геометрических объектов – это наиболее распространенная проблема, возникающая при использовании трёхмерного решателя. А также в рамках статьи погрузимся в основные аспекты работы программиста конечного приложения с С3D Solver, рассмотрим функциональность математической библиотеки и пройдём путь от клика по иконке до сопряжения геометрических объектов на конкретном примере.

Чтобы лучше ориентироваться в предметной области и терминах, которые будут упоминаться, начнём с краткого описания базовых понятий. В статье рассмотрим три представления твёрдых тел. Изображение модели, которую пользователь видит на экране, мы будем называть графическим представлением. Следующее представление – модельное. Оно включает в себя описание топологии моделируемого объекта, связей элементов геометрической модели, историю её построения и атрибуты элементов. За него отвечает геометрическое ядро C3D Modeler. Наконец, есть параметрическое представление, которое обеспечивает взаимосвязь элементов модели, позволяя редактировать её, синхронно изменяя положение тел. Воплощается оно в системе геометрических ограничений GCM_System под управлением C3D Solver, который не имеет прямой связи с твёрдыми телами модельного представления. Отсюда возникает важная особенность – необходимость синхронизации представлений.

На рисунке 1 показана схема, которая, по нашему мнению, является оптимальной при внедрении C3D Toolkit в конечное пользовательское приложение.

Рисунок 1. Схема взаимодействия представлений геометрических объектов
Рисунок 1. Схема взаимодействия представлений геометрических объектов

Объяснив разницу представлений, мы перейдём к рассказу о самих геометрических объектах и возможностях работы с ними. Структурной единицей параметрической сборки является компонент, который может быть как отдельным телом, так и группой тел, понимаемых как геометрически-жёсткое объединение. Геометрию компонентов или их тел образуют различные трёхмерные примитивы, например цилиндр, сфера, конус, плоскость и твёрдое тело произвольной формы. C3D Solver может менять положение компонентов сборки в пространстве под воздействием взаимосвязей между ними. Эти взаимосвязи выражаются несколькими видами ограничений, среди которых логические и размерные. К первому типу могут относиться касания, симметрия, совпадения, а ко второму – угловые и линейные размеры, а также паттерны. Конечно, для иллюстрации мы приводим далеко не полный список поддерживаемых ограничений. У пользователя есть возможность применить инструменты диагностики системы ограничений, анализа степеней свободы, драггинга и логгирования вызовов API, о некоторых из них расскажем в этой статье.

Чтобы нагляднее продемонстрировать взаимодействие программиста с нашей библиотекой, мы используем C3D Vision для создания простого демонстрационного приложения. Модуль C3D Vision помогает в визуализации и взаимодействии с трехмерными моделями в инженерном приложении. Он обеспечивает качественную отрисовку геометрии, оптимизируя скорость работы с большими сборками за счёт использования программных и аппаратных средств. Также модуль предоставляет набор инструментов для построения трёхмерных сцен. C3D Vision обеспечивает кроссплатформенность приложения. Поддерживаются операционные системы Windows и Linux.

Основные элементы интерфейса, которые нам понадобятся, представлены на рисунке 2.

Рисунок 2. Графический интерфейс демонстрационного приложения
Рисунок 2. Графический интерфейс демонстрационного приложения

В нашем демонстрационном примере под цифрой 1 находится панель инструментов, позволяющая наложить ограничения. Давайте остановимся на параллельности и на её примере рассмотрим, как всё устроено.

Рисунок 3. Ограничения, представленные на панели демонстрационного приложения
Рисунок 3. Ограничения, представленные на панели демонстрационного приложения

В середине экрана (область, обозначенная цифрой 2) расположена основная рабочая зона – сцена, служащая для отображения объектов. Для примера добавим два параллелепипеда.

Рисунок 4. Добавленные в рабочую зону модели
Рисунок 4. Добавленные в рабочую зону модели

В областях окна, обозначенных цифрами 3 и 4, выводится информация для работы со сборкой и её диагностики. К ним мы вернёмся в конце статьи, когда коснёмся возможностей вспомогательных инструментов.

Взаимодействуя с панелью выбора ограничений, на программном уровне мы переходим к классу, ответственному за обработку событий в нашем примере. Назовём его SceneWidget. Однако не будем вдаваться в подробности его реализации, так как это выходит за рамки темы нашей статьи. В демонстрационном примере работа с событиями происходит через процессы. Определим PrBase – родительский класс для всех процессов. В нём объявлены все функции, которые понадобятся нам для работы с процессами. Продемонстрируем основные методы класса PrBase.

class PrBase : public VSN::Node
{
public:
    virtual bool IsReady() const = 0;
    virtual void CreateObject() = 0;
    virtual void CancelObject() = 0;

protected:    
    AssemblySolver& GetSolver();
    void UpdateAssemblyScene();
};

Для взаимодействия с пользователем в SceneWidget определим enum Commands, в котором перечислим все возможные команды. Для наглядности покажем некоторые из команд, относящихся к ограничениям.

enum Commands
{
    Coincident,
    Coaxial,
    Parallel,
    Distance,
    Angular,
};

SceneWidget реагирует на событие, когда мы нажимаем на иконку. В результате этого действия будут созданы процессы для обработки событий в функции viewCommands, которая могла бы выглядеть так, как показано ниже. В ней решается, какой процесс инициализировать. Сосредоточимся на параллельности и посмотрим, что произойдёт.

void SceneWidget::viewCommands(Commands cmd){
    switch (cmd)
    {
        // ...
        case SceneWidget::Parallel:
        {
            if (m_pCurrentProcess != nullptr)
            {   
                m_pCurrentProcess->CancelObject(); // закроем текущий процесс
                VSN_DELETE_AND_NULL(m_pCurrentProcess);
            }
            m_pCurrentProcess = new PrBinConstraint(GCM_PARALLEL, this); //создадим новый процесс
        }
        break;
    }
}

Для наложения ограничения, в частности параллельности, реализуем PrBinConstraint – это наследник базового класса PrBase. В качестве входного параметра ему посылается тип ограничения. Это позволяет понять, какое именно ограничение нужно наложить. Переопределим метод CreateObject(), при вызове которого будет создаваться ограничение.

void PrBinConstraint::CreateObject()
{
    PrBase::CreateObject();
    if (IsReadyToCreate())
    {
        switch (ConstraintType())
        {
            case PARALLEL:
            {
          conId = GetSolver().AddConstraint( ConstraintType(), geomOne.second, geomTwo.second, 0.0, GCM_CLOSEST );
            }
            break;
            // ...
        }
        GCM_result result = GetSolver().Evaluate();
        if (result == GCM_RESULT_Ok)
        {
            UpdateAssemblyScene();
        }
        else
        {
            GetSolver().RemoveConstraint(conId);
        }
    }
}

Теперь кратко опишем его работу. Сначала проверяется корректность присланных аргументов ограничения. В зависимости от типа ограничения будет выполнен соответствующий блок кода. Как условились раннее, мы накладываем ограничение параллельности. Для сопряжения геометрических объектов вызываем функцию AddConstraint. В качестве параметров передаются тип ограничения и объекты, на которые накладывается ограничение. Затем для параметрической модели вызывается функция Evaluate, которая решает систему ограничений. Если ограничение вычисляется успешно, то далее обновляется сцена, то есть графическое представление. За это отвечает функция UpdateAssemblyScene(). Иначе ограничение удаляется.

Важно отметить, что работа с C3D Solver осуществляется не напрямую, а через класс-обёртку – это видно по вызову GetSolver(). Расскажем подробнее о классе-обёртке и о том, как его можно было бы реализовать.

Между графическим приложением и C3D Solver определим посредника в виде класса-обёртки – AssemblySolver. В нём объединим модельное и параметрическое представления. Именно этот класс будет отвечать за взаимодействие между ними. В глобальном смысле AssemblySolver обеспечит решение задачи на компоновку сборки. Он позволит соединять компоненты сборки с использованием системы ограничений. Обратимся к содержанию AssemblySolver, а именно к закрытым полям данных класса.

class AssemblySolver
{
public:
    // ...
    // Функции-члены класса.
    // ...
private: // Поля данных
    GCM_system  m_gcSystem;
    SPtr<MbAssembly> m_assmModel;
    // Зарегистрированные геометрические объекты, которые используются как геометрические компоненты сборки.
    std::map<SPtr<const MbItem>, GCM_geom> m_compGeoms;
    // Зарегистрированные подобъекты, которые используются в качестве аргументов геометрических ограничений.
    std::map<SPtr<const MbTopItem>, GCM_geom> m_subGeoms;
};

Как уже было сказано, класс AssemblySolver агрегирует два представления: модельное и параметрическое. В GCM_system находится параметрическое представление. Что касается модельного, то в AssemblySolver хранится модель сборки, представленная классом MbAssembly. Он служит контейнером для всех тел модельного представления. Словари m_compGeoms и m_subGeoms будут обеспечивать связь между объектами модели и их параметрическим представлением.

Для передачи в методы AssemblySolver информации о модельном представлении будем использовать вспомогательный класс AsMatingGeom. Он предназначен для представления геометрических элементов в контексте сборки и обеспечивает механизм для идентификации и работы с составляющими компонентов. В данных класса мы заведём указатель на исходное тело и путь от самого тела до грани, ребра или вершины. Также определим необходимые для работы методы. Component() будет возвращать указатель на геометрический объект – аргумент ограничения. Создадим функцию SubGeom(), которая по компоненту и относительному пути выдаст подобъект (грань, рёбро, вершину, которые являются частью родительского тела). SubGeomRecord() будет создавать отображение и конвертировать вершину, кривую или поверхность в параметрическое представление, с помощью которого геометрический объект можно зарегистрировать. Не станем заострять внимание на реализации методов, поскольку это выходит за рамки нашей статьи, однако покажем, как мог бы выглядеть класс AsMatingGeom.

class AsMatingGeom
{
public:
  AsMatingGeom(const MbItem & component, const MbPath & path);
  SPtr<const MbItem> Component() const;
  SPtr<const MbTopologyItem> SubGeom() const;
  GCM_g_record SubGeomRecord() const;

private:
  SPtr<const MbItem> m_compItem;     // указатель на родительский объект
  MbPath             m_subItemPath;  // путь от компонента до сопрягаемой геометрии
};

Получается, что этот тип хранит всю нужную нам информацию об объекте на сцене, а AssemblySolver, в свою очередь, хранит связи между этим объектом и его представлением в виде словарей. Извне в класс-обёртку приходит модельное представление в виде AsMatingGeom, а он преобразует его в параметрическое представление – GCM_geom.

Остановимся на интерфейсе класса-обёртки и поговорим об основных методах, которые нам потребуются: AssemblySolver должен сопрягать геометрические объекты, следить за их размерами и позицией, а также реагировать на внесённые изменения. В его ответственности будет синхронизация модельного и параметрического представлений моделей. Отдельно отметим вспомогательные функции, которые позволят и записывать информацию о сборке в файл, и получать информацию об ограничениях. Реализованные методы перечислим ниже.

class AssemblySolver
{
public:
    GCM_constraint AddConstraint(GCM_c_type, const AsMatingGeom&, const AsMatingGeom&, VariantPar par1, VariantPar par2 = GCM_CLOSEST);
    void RemoveConstraint(GCM_constraint);
    GCM_result Evaluate();
    void UpdatePositions(GCM_result evalRes);
    GCM_geom QueryGeomId(const AsMatingGeom&);
    void SubscribeLogs(GCM_log_func, GCM_extra_param) const;
    // ... методы
private:
    QueryComponentId(SPtr<const MbItem> compItem);
};

Описав концепцию класса-обёртки, мы вернёмся к процессу создания ограничения. Обратимся к одной из представленных функций, которую мы неоднократно упоминали выше, – AddConstraint. В результате её выполнения накладывается ограничение. На вход она принимает тип ограничения, объекты, на которые накладываются ограничения, и структуру, которая хранит характеристики ограничений. Покажем, как она работает при наложении параллельности.

GCM_constraint AssemblySolver::AddConstraint(GCM_c_type cType, const AsMatingGeom& mtGeom1, const AsMatingGeom& mtGeom2, VariantPar par1, VariantPar par2 )
{
    GCM_constraint conId = GCM_NULL;
    GCM_geom gArg1 = QueryGeomId( mtGeom1 ); // запрос выдаёт ID геометрического объекта в терминах C3D Solver
    GCM_geom gArg2 = QueryGeomId( mtGeom2 );
    switch(cType)
    {
        // ...
        case GCM_PARALLEL:
        {
            conId = GCM_AddBinConstraint( m_gcSystem, cType, gArg1, gArg2, par1.value.alignVal, par2.value.tanChoice );
        }
        break;
        // ...
    }   
}

В зависимости от типа ограничения, которое мы хотим наложить, напрямую вызывается нужная функция из API C3D Solver. AddConstraint возвращает дескриптор нового ограничения, зарегистрированного в системе.

Здесь же мы используем функцию QueryGeomId. Чтобы работать с ограничениями, необходимо знать ID тела. Именно эта функция отвечает за регистрацию геометрического тела в C3D Solver.

Напишем, как можно было бы её реализовать. Пусть регистрация геометрического объекта состоит из двух частей. Если подобъект был зарегистрирован раньше, то функция QueryGeomId выдаст его ID.

GCM_geom AssemblySolver::QueryGeomId(const AsMatingGeom& mtGeom)
{
  auto subGeom = m_subGeoms.find(mtGeom.SubGeom());
  if ( subGeom == m_subGeoms.end() ) // если не нашли геометрический объект
  {
    auto compId = QueryComponentId(mtGeom.Component());
    if (compId != GCM_NULL && mtGeom.SubGeom() != nullptr)
    {
      GCM_geom subGeomId = GCM_SubGeom(m_gcSystem, compId, mtGeom.SubGeomRecord());
      m_subGeoms[mtGeom.SubGeom()] = subGeomId;
      return subGeomId;
    }
    return compId;
  }
  return subGeom->second;
}

Если геометрический объект ранее не был зарегистрирован, то вызывается метод QueryComponentId. Изначально ищется родительский объект по похожей логике: если тело уже было зарегистрировано в C3D Solver, то функция выдаёт ID; в противном случае она регистрирует его и присваивает ему новый ID.

GCM_geom AssemblySolver::QueryComponentId(SPtr<const MbItem> compItem)
{
  if (compItem == nullptr)
    return GCM_NULL;

  auto solid = m_compGeoms.find(compItem);
  if (solid == m_compGeoms.end())
  {
    MbPlacement3D lcs; compItem->GetPlacement(lcs);
    auto newGeomId = GCM_AddGeom(m_gcSystem, GCM_SolidLCS(lcs));
    m_compGeoms[compItem] = newGeomId;
    return newGeomId
  }
  return solid->second;
}

В коде вы можете заметить вызов API C3D SolverGCM_AddGeom. С помощью него мы объявляем компонент в системе ограничений, проще говоря, регистрируем геометрический объект.

GCM_geom GCM_AddGeom(GCM_system gSys, const GCM_g_record & gRec)

Теперь вернёмся к QueryGeomId и рассмотрим второй этап регистрации. Когда родительский объект был зарегистрирован, при необходимости мы регистрируем подобъект и тогда возвращаем его ID. Это необходимо, поскольку обычно пользователь работает с этими частями куда чаще. Для этого мы обратились к функции API GCM_SubGeom. Она регистрирует грани, рёбра, вершины, указывая при этом, что они являются частями более общего родительского объекта. Обычно именно на эти элементы накладываются ограничения. Накладывая ограничения, важно понимать, что С3D Solver работает с бесконечными геометрическими поверхностями и кривыми, то есть для него грань — это, например, плоскость, имеющая начало координат и нормаль, а ребро – бесконечная прямая.

GCM_geom GCM_SubGeom(GCM_system gSys, GCM_geom lcs, const GCM_g_record & gRec).

После того как отработала функция AddConstraint, наступает очередь синхронизации, которая также проходит в методе PrConstraint::CreateObject(). Чтобы обновлять геометрию после решения, напишем несколько функций. Evaluate непосредственно решит систему ограничений. Она вычислит новое состояние геометрии и раздаст диагностические коды тем ограничениям, которые не удалось удовлетворить.

GCM_result AssemblySolver::Evaluate()
{
    GCM_result evalRes = GCM_Evaluate(m_gcSystem);
    if (evalRes == GCM_RESULT_Ok)
    {
        UpdatePositions(evalRes);
    }
    return evalRes;
}

Если функция API решателя GCM_Evaluate() вернёт код ошибки GCM_RESULT_Ok, то мы заходим в функцию обновления состояния UpdatePositions. С её помощью происходит синхронизация положений объектов для модельного и параметрического представлений.

void AssemblySolver::UpdatePositions
{    
    for ( auto && compGeom : m_compGeoms )
    {
        if (const MbItem* compItem = m_assmModel->SubItem(compGeom.first))
        {
   const_cast<MbItem*>(compItem)->SetPlacement(GCM_Placement(m_gcSystem,compGeom.second));
        }
    }
}

Здесь используется функция API GCM_Placement. Она возвращает новое положение геометрического объекта, которое приписывается конкретному телу, отображаемому на сцене.

Заметим, что необходимо обновлять положение только родительского компонента, потому как подобъекты находятся в его локальной системе координат. Если будет правильно изменено положение родительского тела, то все его подобъекты будут перемещены корректно.

 Рисунок 5. Модели после наложения ограничения параллельности
Рисунок 5. Модели после наложения ограничения параллельности

Теперь, когда мы рассказали о механизме синхронизации состояний, вернёмся к полезным возможностям нашего примера, о которых упоминали в начале статьи. Давайте обратимся к рисунку 5. Область List Constraints, которую мы обозначили цифрой 3 на рисунке 2, отображает панель управления ограничениями, которые были наложены на объекты в процессе моделирования. Здесь отображаются накладываемые ограничения, здесь же их можно редактировать и удалять.

Рисунок 6. List Constraints
Рисунок 6. List Constraints

Для отладки и работы со сборками в C3D Solver доступен инструмент логирования API. Он позволяет записать вызовы API и прислать их нам, разработчикам, чтобы по нему можно было однозначно воспроизвести проблемную ситуацию. Заметим, что логирование происходит с использованием названий настоящих функций из API. Например, при регистрации геометрического объекта мы можем увидеть вызов:

(GCM_AddGeom (GCM_LCS (0.0 0.0 56.853530883789063)(0.0 0.0 1.0)(1.0 0.0 0.0)(0.0 1.0 0.0)) #2)

Существует два способа логирования. Первый из них – в режиме реального времени. В моменты, когда на геометрическое тело накладывается ограничение, окно Journal Output (обозначенное на рисунке 2 цифрой 4) перестаёт пустовать. Там появляются те или иные выражения, отражающие вызовы API. За этот способ логирования отвечает функция API:

GCM_SubscribeJournal(m_gcSystem, logFunc, extra);

В окне Journal Output (рисунок 5) выводится этот лог. Процесс вывода активируется при задействовании вызовов API C3D Solver.

Рисунок 7. Journal Output
Рисунок 7. Journal Output

Второй способ – записывать информацию о вызовах API всего сеанса работы в файл. Он реализован в функции SetJournal.

bool GCM_SetJournal (const char *fName)

В нашей статье мы постарались доступно рассказать о том, как взаимодействовать с C3D Solver, остановиться на необходимости синхронизации представлений тел, чтобы помочь разобраться с проблемами, которые возникают у разработчиков приложений на основе наших продуктов. А также описали, как один клик по иконке может запустить цепочку событий, результатом которой стало сопряжение геометрических объектов.

Попробовать инструмент логирования и редактирование ограничений в панели управления List Constraints, а также полную функциональность нашего демонстрационного приложения можно в шестидесятом примере библиотеки C3D Vision. На основе этого примера был проведён мастер-класс по встраиванию C3D Solver в пользовательское приложение, где мы создавали параметрическую сборку, подробно демонстрировали возможности C3D Solver и останавливались на функциях API. Также рекомендуем обратиться к документации C3D Toolkit, которая содержит раздел «Геометрические ограничения трёхмерных объектов». Здесь вы сможете подробнее изучить функционал нашей библиотеки и разобраться с классами, типами данных и их взаимосвязями.

Александр Алахвердянц

Ведущий математик-программист
C3D Labs

Инна Макарова

Стажер в команде C3D Solver
C3D Labs

Теги:
Хабы:
Всего голосов 2: ↑2 и ↓0+3
Комментарии0

Публикации

Информация

Сайт
ascon.ru
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия