2 - 2011

Написание прикладных программ в среде C++ Builder с использованием COM-модели Siemens Femap

А.Г. Яшутин

Компания Siemens с недавнего времени начала интенсивно продвигать на рынке программный продукт Siemens Femap CAE. Последняя, десятая по счету версия Femap обладает большим количеством новых удобных инструментов для пре­ и постпроцессора конечно­элементного анализа, не уступая по возможностям таким «тяжеловесам», как ANSYS, Patran и SimExpert. При этом Femap является настоящим Windows­приложением с точки зрения как интерфейса (классические панели меню, инструментов, диалоговые окна в стиле Windows), так и архитектуры. Сегодня Siemens Femap предоставляет полноценную поддержку технологии COM (Component Object Model), что позволяет использовать его как внешний сервер (COM­сервер) и управлять им из клиентского приложения (COM­клиента).

Несмотря на то что внедрение языка сценариев в сложные CAD/CAM/CAE­приложения является сегодня негласным правилом, удобство использования и внутренние возможности этих языков очень сильно различаются. Несомненным достоинством Femap в этом плане является возможность применения любого языка программирования, поддерживающего COM, в любой среде программирования. В состав Femap уже входит редактор WinWrap Basic, позволяющий полноценно использовать все возможности Femap, а также другие приложения в качестве COM­серверов. Написанные на WinWrap Basic тексты программ (сценарии) могут быть помещены для быстрого доступа в панель Custom Tools и запускаться как встроенные функции Femap, при этом на выполняемые сценарии не налагаются ограничения, связанные с применением COM.

Минусом такого подхода является то, что мы имеем дело с  транслятором языка Basic. Основной недостаток трансляторов — это то, что перед непосредственным выполнением происходит разбор текста, создание списка команд и т.д. В отличие от трансляторов, любой компилятор создает исполняемый файл, написанный в машинном коде: любая аналогичная программа в случае компиляции будет работать значительно быстрее. Другим недостатком является сам язык Basic: несмотря на то что современные версии этого языка предоставляют средства и для объектно­ориентированного программирования, он так и остается языком программирования для начинающих. Вместе с тем создание сложных диалоговых окон в среде WinWrap Basic весьма затруднено.

Таким образом, для написания относительно простых и часто используемых сценариев, запускающихся как встроенные функции, WinWrap Basic подходит практически идеально, но для создания более сложных и независимых программ, реализующих сложную логику и запускающихся чаще для анализа каких­то отдельных, специально созданных моделей, лучше применять компиляторы сторонних производителей (например, Microsoft, Borland, CodeGear и Intel).

Для демонстрации использования интегрированной среды разработки C++ Builder, известной своей легкостью и мощью в получении визуальных компонентов, а также надежностью кода, создадим программу, позволяющую применять адаптивную сетку.

Программа должна получить из упругого решения (linear static) данные о перемещении узлов модели и на их основании переформировать сетку конечно­элементной модели. Как результат работы программы мы получим модель в деформированном состоянии. В будущем уже измененная модель может быть использована для дальнейших расчетов или модификации КЭ­сетки, например, в местах контакта для получения более точных результатов на дальнейших этапах расчета. Модель деформированного состояния может применяться в задачах изнашивания, в нелинейных задачах больших перемещений, в создании систем следящих сил, а также в ряде других научных работ.  Этот пример хорошо демонстрирует работу с самой конечно­элементной моделью, а также с результатами расчета, полученными из какого­либо решателя (в нашем случае — NX Nastran). В качестве среды разработки использовался C++ Builder 2007.

Для начала создадим новый проект: File®New®VCL Form Application — C++ Builder.  Появится новая пустая форма. На нее из панели Tools Palette (по умолчанию, правый нижний угол) поместим кнопку (стандартные кнопки расположены во вкладке Standard и имеют название TButton). Теперь изменим надпись на кнопке, для чего на панели Object Inspector (по умолчанию, левый нижний угол) в выпадающей вкладке выберем пункт Button1 TButton. Затем в окне Properties найдем пункт Caption и внесем свое название этой кнопки (например, Transform Mesh). Можно изменять размер кнопки, пользуясь маркерами на самой кнопке или вводя свои значения в пунктах Height и Weight всё в том же окне Object Inspector. Теперь форма нашего приложения приняла вид, показанный на рис. 1.

Рис. 1. Форма приложения в окне среды разработки C++ Builder

Рис. 1. Форма приложения в окне среды разработки C++ Builder

Теперь необходимо импортировать компоненты библиотеки Femap в созданный проект. Для этого в пункте панели меню Component выберем пункт Import Component. В появившемся окне Import Component — Type of Component (рис. 2) отметим радиокнопку Import a Type Library и нажмем Next.

Рис. 2. Диалоговое окно Type of Component

Рис. 2. Диалоговое окно Type of Component

Из появившегося списка следует выбрать необходимую нам библиотеку программы Femap (если на компьютере было установлено несколько версий, то вполне вероятно, что и библиотек компонентов Femap будет несколько. В этом случае следует выбрать библиотеку той версии, в которой обычно ведется работа. В нашем случае это Femap 10.1) — рис. 3.

Рис. 3. Диалоговое окно Registered Type Library

Рис. 3. Диалоговое окно Registered Type Library

Если же по какой­то причине в этом списке не оказалось нужной библиотеки, то можно выбрать ее вручную. Для этого нужно нажать на кнопку Add и найти файл с названием Femap.tlb. Этот файл хранится в директории, куда был установлен Femap. Снова нажмите кнопку Next.

В окне Component, показанном на рис. 4, следует указать пути сохранения импортированных файлов. Лучше использовать какую­нибудь специально созданную для этого директорию внутри папки проекта. В нашем случае это директория Import. Можно отметить галочкой пункт Generate Component Wrapper для создания классов­оберток импортированных компонентов. Нажмите кнопку Next. В последнем окне отметьте пункт Add unit to… project для автоматического связывания импортированных файлов с вашим проектом. Нажмите кнопку Finish (рис. 5).

Рис. 4. Диалоговое окно Component

Рис. 4. Диалоговое окно Component

Теперь в ваш проект были добавлены необходимые файлы с именами femap_TLB и femap_OCX. В рассмотренном нами примере файлы femap_OCX использоваться не будут, и по этой причине их можно удалить из проекта, чтобы уменьшить время компиляции и сэкономить на размере получаемого кода. Для удаления файлов из проекта удобно пользоваться панелью Project Manager (по умолчанию, правый верхний угол). Для этого выделите файл в дереве, щелкните правой клавишей мыши и выберите из контекстного меню пункт Remove From Project (рис. 6).

Рис. 5. Диалоговое окно Create Unit

Рис. 5. Диалоговое окно Create Unit

Рис. 6. Работа с панелью

Рис. 6. Работа с панелью Project Manager

Выполните сохранение проекта File®Save Project As…. Можете задать свои имена для файлов проекта, в данном примере во избежание путаницы используются имена по умолчанию.

Перейдите в редакторе кода на вкладку Unit1.cpp. Внизу появятся дополнительные вкладки, среди которых есть вкладка на заголовочный файл Unit1.h. В коде заголовочного файла впишите следующую директиву препроцессора: #include <femap_TLB.h>. Это позволит применять в вашем проекте классы, описанные в файле femap_TLB.h.

Для работы с объектами COM в среде C++ Builder существует несколько методов. Для нашего проекта будем применять наиболее простой метод использования указателей на «ко­классы» (CoClasses). Объявим новую переменную — указатель на класс Imodel, носящий название ImodelPtr. Теперь файл Unit1.h имеет следующий вид:

 

#ifndef Unit1H

#define Unit1H

//­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

#include <Classes.hpp>

#include <Controls.hpp>

#include <StdCtrls.hpp>

#include <Forms.hpp>

 

#include <femap_TLB.h>

//­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

class TForm1 : public TForm

{

__published:   // IDE­managed Components

      TButton *Button1;

private:   // User declarations

      ImodelPtr pModel; //указатель на класс Imodel

public:      // User declarations

      __fastcall TForm1(TComponent* Owner);

};

//­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

extern PACKAGE TForm1 *Form1;

//­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­

#endif

 

Основным классом объектной модели Femap является класс Imodel, который представляет собой своеобразную обертку для работы с текущей базой данных в приложении Femap. Вызов всех стандартных окон, запросы данных о геометрии модели, сетке, выходных данных и текущих настройках осуществляются именно через этот класс.

Очередной экземпляр программы должен создаваться при запуске нашей программы. Для этого надо добавить обработку события создания формы.

На панели Object Inspector выберите Form1 TForm1 из выпадающего меню, затем откройте вкладку Events, найдите событие OnCreate и дважды щелкните левой кнопкой мыши по пустому полю справа, чтобы C++ Builder автоматически добавил обработчик события создания формы. Отредактируйте функцию TForm1::FormCreate, чтобы она приняла следующий вид:

 

void __fastcall TForm1::FormCreate(TObject *Sender)

{

pModel = Comodel::Create(); // Создаем новый экземпляр программы Femap

pModel­>feAppVisible(true); //Делаем его видимым  

}

 

Теперь можно использовать всю объектную модель Femap. В новом открытом окне программы вы можете создать новую модель или открыть старую, вносить изменения в модель и пр., а в нужный момент получить из своего приложения полный доступ к текущей модели. Необходимо отметить, что закрытие нашей программы приведет к закрытию экземпляра Femap, что, собственно, не очень удобно — такова плата за применение COM.

Для удобства, чтобы не переключать окна, можно попробовать вставить свое окно в окно программы Femap. Для этого следует изменить код функции TForm1::FormCreate следующим образом:

 

void __fastcall TForm1::FormCreate(TObject *Sender)

{

   pModel = Comodel::Create();//создаем новый экземпляр Femap

 

   HANDLE hWnd; //ссылка на окно

   int WinID;  //Переменная для передачи ссылки на окно

   hWnd = Form1­>Handle; //получаем ссылку на текущее окно

   WinID = (int)hWnd;  //явное приведение к типу int

 

   //встраиваем нашу форму (окно приложения) в окно Femap:

   pModel­>feAppRegisterAddInPane(true, WinID, 0, false, false, 3, 2);

   pModel­>feAppVisible(true); //Делаем Femap видимым

}

 

Обратите внимание на функцию feAppRegisterAddInPane — именно в ней описывается внедрение окна нашего приложения в окно программы Femap, ее местоположение и внедрение в другие панели Femap, наличие в заголовке панели кнопок Ѕ для закрытия окна и в виде канцелярской кнопки для возможности прятать панель. Подробное описание функции и ее параметров вы найдете в FEMAP API Reference, входящей в справку программы Femap.

После того как окно нашей программы будет закрыто, необходимо «вынуть» окно нашей программы из Femap обратно. Отметим, что при закрытии нашей программы автоматически закроется и Femap, причем без уведомления о сохранении результатов работы в модели. Не лишним будет добавить функцию сохранения модели перед уничтожением экземпляра программы. Для этого подобно тому, как создавался обработчик создания формы TForm1::FormCreate, следует создать обработчик события закрытия окна (событие OnDestroy).

 

void __fastcall TForm1::FormDestroy(TObject *Sender)

{

   pModel­>feFileExit(); //закрываем файл и спрашиваем

                     //о сохранении результатов

 

   HANDLE hWnd; //ссылка на окно

   int WinID;  //Переменная для передачи ссылки на окно

   hWnd = Form1­>Handle; //получаем ссылку на текущее окно

   WinID = (int)hWnd;  //явное приведение к типу int

 

   //“вынимаем” наше приложение из Femap

   pModel­>feAppRegisterAddInPane(false, WinID, 0, false, false, 3, 2);

 

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

Откройте форму приложения и дважды щелкните по кнопке Transform Mesh. C++ Builder автоматически создаст обработчик события OnClick кнопки.

Прежде всего нам необходимо узнать перемещения узлов модели, полученные из решения статической задачи. Эта информация хранится в так называемых Output (результатах расчета), объединенных в Output Set (наборы решений). Необходимо дать пользователю возможность выбрать нужный Output Set, а затем на основании этого набора результатов переформировать сетку.

Данная задача может быть реализована следующим образом:

 

void __fastcall TForm1::Button1Click(TObject *Sender)

{

   /* ­­­ Этап 1. Получение доступа к результатам расчета. ­­­*/

 

   /*   Создаем указатель на объект OutputSet и присваиваем

      ему значение OutputSet нашей модели: */

   IOutputSetPtr pOutPutSet = pModel­>feOutputSet;

 

   /*   Создаем указатель на объект Output и присваиваем

      ему значение Output нашей модели: */

   IOutputPtr pOutPut = pModel­>feOutput;

 

   //возвращаемое функциями значение

   int rc;

   //ID текущего Output

   int OutPutId;

 

   ISetPtr OS_set = pModel­>feSet;

   rc = OS_set­>Select(FT_OUT_CASE, True,

      WideString(“Выберите OutputSet, из которого следует загрузить/ перемещения узлов»).c_bstr());

 

   /*   Проверяем, осуществился ли выбор OutputSet удачно

      и выбран ли только один элемент */

   if(rc == FE_OK && OS_set­>Count() == 1)

   {

      pModel­>feAppMessage(FCM_NORMAL, WideString(«Правильные параметры функции»).c_bstr());

      OS_set­>Reset();// сбрасываем счетчик сетов

 

      /*    Устанавливаем указатель на

         первый (и единственный) выбранный элемент: */

      OS_set­>Next();

 

      /*Получаем ID выбранного OutputSet: */

      OutPutId = OS_set­>get_ID();

 

      /*    Делаем активным для получения данных

         набор с номер “1” (перемещения) в текущем

         OuputSet’е : */

      rc = pOutPut­>GetFromSet(OutPutId, 1);

 

      //количество элементов в массивах вектора перемещений:

      int listcount = 0;

       //Массивы компонентов вектора перемещений

      TVariant x, y, z;

      //массив ID­узлов, для которых получаем перемещения

      TVariant ID;

 

      //Считываем параметры:

      rc = pOutPut­>GetVectorAtNode(&listcount, &ID, &x, &y, &z );

 

      if(listcount>0)

         pModel­>feAppMessage(FCM_NORMAL, WideString(“Получили параметры liscount”).c_bstr());

 

      //Получаем указатели на массивы данных результатов расчета,

      //а также ID­узлов.

      //Указатель получаем непосредственным приведением к типу:

 

      // указатель на массив с ID:

      int *ID1 = (int*)VarArrayLock(&ID);

      //указатель на массив с перемещениями по оси X:

      double* x1 = (double*)VarArrayLock(&x);

      //указатель на массив с перемещениями по оси Y:

      double* y1 = (double*)VarArrayLock(&y);

      //указатель на массив с перемещениями по оси Z:

      double* z1 = (double*)VarArrayLock(&z);

 

      /* ­­­ Этап 2. Получение ссылки на узлы модели и

      переформирование сетки  на основе полученных результатов: ­­*/

 

      //Создаем указатель на объект узла КЭ­модели и присваиваем

      //ему значение узлов нашей модели:

      INodePtr pNode = pModel­>feNode;

 

      //ID текущего элемента:

      int CurrentNodeID = 0;

 

      for(int i=0; i<listcount; i++)

      {

 

         CurrentNodeID = ID1[i];//Получаем ID очередного узла

 

         //Делаем узел с указанным ID активным:

         pNode­>Get(CurrentNodeID);

 

         //Изменяем координаты активного узла согласно

         //полученным для него перемещениям:

         pNode­>x += x1[i];

         pNode­>y += y1[i];

         pNode­>z += z1[i];

 

         //Сохраняем координаты узла и вносим их в базу данных:

         pNode­>Put(CurrentNodeID);

 

         //Выводим информацию об изменненом узле в панели Messages:

         AnsiString s = “Узел с ID = ”;

         s += CurrentNodeID;

         s += “ был успешно перемещен.”;

         pModel­>feAppMessage(FCM_NORMAL, WideString(s).c_bstr());

      }

 

   }

   else

   {

      //В случае, если выбран более чем один Output Set или

      //не был выбран ни один элемент, сообщаем об этом пользователю:

      AnsiString s = “Неправильные параметры функции”;

      pModel­>feAppMessage(FCM_NORMAL, WideString(s).c_bstr());

   }

}

 

На первом этапе мы получаем доступ к данным расчета, уже хранящимся в базе данных модели Femap. Для выбора нужного нам набора результатов (Output Set) следует создать так называемый набор (Set), содержащий ID­номера необходимых нам сущностей (это могут быть ID­номера узлов (Node), точек (Point), элементов (Element), наборов нагрузок (Load Set) и т.д., в том числе и наборы решений (Output Set)). Вносить данные в набор Set мы будем с помощью метода Select, вызванного для объекта типа Set. Этот метод вызывает стандартное окно выбора Femap. Среди параметров, принимаемых функцией Select, есть параметр FT_OUT_CASE, сообщающий Femap, что выбирать будем именно Output Set. Другие значения этого параметра приводят к выбору других сущностей модели (точек, линий, узлов, элементов и т.д.). Следует отметить, что в объекте Set хранятся только ID­номера сущностей, без привязки к их типу, поэтому лучше создавать отдельный набор Set для каждой сущности модели.

Получив ID нужного нам Output Set, готовим нужную информацию для считывания с помощью функции GetFromSet объекта Output. Объект Output содержит различные массивы данных о перемещениях, напряжениях, температуре и прочем в узлах и элементах конструкции. Первый параметр функции — ID текущего Output Set, второй — номер считываемого массива данных. В нашем случае это «1», что соответствует перемещениям узлов в файле вывода. Затем мы вызываем метод GetVectorAtNode, с помощью которого получаем данные о перемещениях в узлах модели. Все параметры данной функции передаются по ссылке, а не по значению. Первый параметр получает количество узлов, для которых определены значения вектора, второй — массив ID­номеров узлов, а три последующих — значения компонентов вектора перемещений соответствующих узлов в глобальной системе координат.

Типы данных

Тип данных, принятый
в руководстве пользователя

Описание типа

Соответствующий тип данных в С++ Builder

BOOL

Однобайтовый тип данных принимает значения true/false

bool

INT2

2-байтовое целое

short

INT4

4-байтовое целое

int (long)

REAL4

4-байтовое число
с плавающей точкой

float

REAL8

8-байтовое число
с плавающей точкой

double

STRING

Строковой тип данных

BSTR

С помощью метода pModel­>feAppMessage мы можем выводить сообщения в панели Messages, обычно расположенной в нижней части окна Femap. Вывод сообщения ведется через преобразование стандартной строки к типу Basic String (BSTR), принятому в COM в качестве стандарта для обмена текстовой информацией.

Очередной особенностью использования COM­модели в C++ Builder служит применение класса TVariant для работы с массивами данных. Этот класс описывает переменную (или массив данных), тип которой можно менять во время выполнения программы. Если объект класса TVariant предназначается для хранения и передачи массива данных (как в нашем случае), то размер этого массива может меняться во время выполнения программы (то есть является динамическим). Это, по большому счету, очень удобно, но динамические массивы занимают больше памяти и работают медленнее, чем стандартные (статические) массивы C/C++. Именно для этого среди функций для работы с массивами класса TVariant существует функция VarArrayLock, принимающая в качестве аргумента ссылку на объект класса TVariant. Данная функция блокирует массив для изменения размера, то есть переводит его из динамического в статический. Функция возвращает указатель уже на статический массив. Для корректной работы следует привести этот указатель к указателю на нужный тип (мы это сделали с помощью прямого приведения типа).

Следует отметить, что функции Femap используют типы данных, приведенные в таблице.

После получения данных о перемещении узлов начинается изменение сетки модели. Для этого создается указатель на узлы модели, делается активным узел с нужным нам ID, изменяются координаты этого узла с помощью свойств x, y, z, а затем уже измененный узел вносится в базу данных модели.

Запустите программу на выполнение — откроется новое окно Femap со встроенным окном нашего приложения, затем откройте нужную вам КЭ­модель (или создайте новую), задайте нагрузки (Loads), определите закрепления (Constraints), проведите расчет (например, с помощью NX Nastran) и загрузите результаты расчета в модель. Теперь нажмите кнопку Transform Mesh на форме приложения, выберите нужный вам набор данных Output Set, и программа сама изменит сетку (рис. 7). Теперь сетка вашей модели приняла уже деформированное состояние.

Рис. 7. Результаты работы программы на тестовом примере

Рис. 7. Результаты работы программы на тестовом примере растяжения пластины

В заключение отметим, что для повышения скорости работы приложения можно уменьшить количество вызовов функций, связанных с интерфейсом (например, стоит отказаться от частого использования функции feAppMessage), в моменты длительных расчетов — блокировать интерфейс пользователя с помощью команды feAppLock (по окончании расчетов разблокировать интерфейс нужно командой feAppUnlock) или полностью скрывать графический интерфейс Femap посредством уже известной команды feAppVisible. После окончания работы вашей программы снова можно сделать окно Femap видимым, а панели команд — доступными для работы.

САПР и графика 2`2011