Creating a CAE Plugin – Understanding the Pointwise Grid Model API (Part 1)

The main purpose of a CAE plugin is to export a grid model. To do this, the plugin must convert the grid data as represented by Pointwise into a format supported by the solver the plugin is targeting. To do this conversion properly, you must have a thorough understanding of the Pointwise grid model (PWGM).

For this post, I will focus on the unstructured PWGM. The structured PWGM will be covered in a future post. While there is much in common between the structured and unstructured PWGM, there are fundamental differences that make them better covered in two articles rather than one.

Grid Handles

Through out this post, I will be referring to Handles. Handles are a compact way to uniquely identify any PWGM entity or element without using pointers. For those of you familiar with C/C++, a Handle can be considered the SDK’s equivalent of a C++ this pointer. The unstructured PWGM supports five Handle types:

  • PWGM_HGRIDMODEL
  • PWGM_HBLOCK
  • PWGM_HDOMAIN
  • PWGM_HVERTEX
  • PWGM_HELEMENT

Each Handle type has one or more SDK functions that is used to query the entity that the Handle represents. The SDK uses a function naming scheme in which the name’s prefix reflects the entity Handle being queried. For example, all functions beginning with PwDom take a PWGM_HDOMAIN as their first parameter (the domain’s this pointer).

Using Handles also reduces the number of arguments needed by SDK functions. Their use also permits simpler function calls in your plugin code. For example, without Handles, a block element function would need three parameters to uniquely identify a given PWGM block element:

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
elementFunction(model, blkIndex, elementIndex, otherArgsHere);
[/sourcecode]

However, using Handles, the function call becomes:

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
elementFunction(hElement, otherArgsHere);
[/sourcecode]

You can also extract information from a Handle using various SDK macros. See the Modules/PWGM-API Opaque Data Handle Types section of the CAE Plugin SDK documentation for the details.

Grid Hierarchy

The unstructured PWGM is arranged in a hierarchy. The root of this hierarchy is the grid model itself. At export, the grid model’s Handle is passed to the plugin’s runtimeWrite() function using the PWGM_HGRIDMODEL model parameter.

[sourcecode language=”cpp” gutter=”true” toolbar=”false” wraplines=”false”]
PWP_BOOL runtimeWrite(CAEP_RTITEM *pRti, PWGM_HGRIDMODEL model, const CAEP_WRITEINFO *pWriteInfo);
// model and pWriteInfo are also available using pRti->model and pRti->pWriteInfo
[/sourcecode]

The unstructured PWGM is divided into two distinct, yet related, views. The first view provides cell centric access to the grid model. The second view provides face centric access to the grid model.

The face centric view API is not available in the current 1.0 R3 version of the Plugin SDK. However, the coding is finished and is being tested. I hope to have it released in the next few weeks.

UPDATE: The face centric view API is now available for use in Pointwise v17.1 + Plugin SDK v1.0 R4 !

In the cell centric view, the grid model consists of

  • One array of Blocks, each with an array of cell Elements and a single Condition.
  • One array of Domains, each with an array of face Elements and a single Condition.
  • One array of Vertices.

In the face centric view, the grid model consists of

  • One array of all Block cell Elements.
  • One array of all cell face Elements, including the faces that lie on the grid’s boundary and the faces that are internal to the grid.
  • One array of Vertices.

It is important to note that the same array of Vertices is used by both the cell centric and face centric views. As hinted to above, the vertices are the common relation that ties the two views together. The complete grid model hierarchy is represented in the figure below.

The grid model hierarchy

The grid model hierarchy

Grid Dimensionality

A gold star for anyone who noticed that the discussion above did not mention 2D grids versus 3D grids. That is because the PWGM makes accessing the grid data (mostly) independent of the grid’s dimensionality.

As in Pointwise itself, the PWGM supports both 2D and 3D meshes. However, there is one key difference. The PWGM provides the same interface to the mesh data independently of its 2D or 3D dimensionality. The uniformity of the PWGM is accomplished using five generic entities; Blocks, Domains, Elements, Vertices and Conditions. Specifically, from the PWGM’s point of view:

  • A Block is collection of block Elements, all of which are assigned a common volume Condition. Each block is identified by a unique, integer index. The indices range from 0 to the number of blocks in the model (Nb) minus 1 (0 … Nb-1).
  • A Domain is a collection of domain Elements, all of which are assigned a common boundary Condition. Each domain is identified by a unique, integer index. The indices range from 0 to the number of domains in the model (Nd) minus 1 (0 … Nd-1).
  • A Vertex is a single XYZ coordinate identified by a unique, integer index. The indices range from 0 to the number of vertices in the model (Nv) minus 1 (0 … Nv-1).
  • An Element is an ordered collection of Vertex indices. The index order defines the Element’s connectivity (see Module/Cell Connectivity).
  • A Condition is a set of two user defined and two solver defined attributes.

A plugin accesses this entity hierarchy using the same function calls. The distinction between 2D and 3D only comes into play in the Elements. For example, the Elements of 3D blocks are hexes, prisms, pyramids, and tets. The Elements of 3D boundary domains are tris and quads. Similarly, the Elements of 2D blocks are tris and quads. The Elements of 2D boundary domains are bars (lines). The tables below summarize the relationship between PWGM grid entities, their Elements and the corresponding Pointwise grid entities.

Elements and Entities

Grid Model element types and Pointwise entity types by dimensionality

Before the grid you created in Pointwise is passed to the plugin, Pointwise boundary entities (domains in 3D or connectors in 2D) sharing the same boundary condition are merged into a single PWGM Domain. As a result, the number of PWGM Domains may not be the same as the number of Pointwise boundary entities, depending on how Pointwise boundary conditions are applied to a grid.

Conversely, Pointwise volumes (blocks in 3D or domains in 2D) sharing a common volume condition are not merged. This means that the number of PWGM Blocks always will be equal to the number of Pointwise volume entities. The figure below illustrates how a 2D Pointwise grid appears to a plugin for cell centric views.

Boundary Entity Merge (cell centric view)

How boundary merging changes a Pointwise grid before it is passed to a plugin (cell centric view).

In the face centric view, the merging of boundary and volume conditions is not meaningful because Blocks and Domains only are accessed as cell and face Elements. The figure below illustrates how the same 2D Pointwise grid appears to a plugin for face centric views.

Face Centric View

How a Pointwise grid appears to a plugin in the face centric view.

Enumerating Entities

The PWGM provides enumerated, random access to all grid entities and their corresponding Elements, except for faces in the face centric view. Using this scheme, an SDK function is provided for each entity or entity Element to ascertain the number of items that exist in the grid model. The count then is used with other related enumeration SDK functions to step through the item hierarchy one by one. For example, the number of grid model blocks is determined using the PwModBlockCount() function. With this count you then enumerate the blocks using the PwModEnumBlocks() function. The PWGM enum functions return a Handle to the requested item. The top, model level function names begin with the PwMod prefix. These function all take a grid model Handle as their input and return either a count or a Handle to an enumerated grid model entity. The top, model level functions currently supported by the unstructured PWGM are

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_UINT32 PwModBlockCount (PWGM_HGRIDMODEL model);
PWGM_HBLOCK PwModEnumBlocks (PWGM_HGRIDMODEL model, PWP_UINT32 ndx);
PWP_UINT32 PwModDomainCount (PWGM_HGRIDMODEL model);
PWGM_HDOMAIN PwModEnumDomains (PWGM_HGRIDMODEL model, PWP_UINT32 ndx);
PWP_UINT32 PwModVertexCount (PWGM_HGRIDMODEL model);
PWGM_HVERTEX PwModEnumVertices (PWGM_HGRIDMODEL model, PWP_UINT32 ndx);
[/sourcecode]

Enumerating the Elements of a Block or Domain is done in a similar manner. First, you get the entity’s Element count using the appropriate Block or Domain Handle (obtained using the functions above). Then, using the count, you enumerate the Elements one by one obtaining each Element’s Handle. The Element’s Handle is used to obtain the Element’s data. The Block and Domain Element functions currently supported by the PWGM are

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_UINT32 PwBlkElementCount (PWGM_HBLOCK block, PWGM_ELEMCOUNTS *pCounts);
PWGM_HELEMENT PwBlkEnumElements (PWGM_HBLOCK block, PWP_UINT32 ndx);
PWP_UINT32 PwDomElementCount (PWGM_HDOMAIN domain, PWGM_ELEMCOUNTS *pCounts);
PWGM_HELEMENT PwDomEnumElements (PWGM_HDOMAIN domain, PWP_UINT32 ndx);
[/sourcecode]

An Element’s data is obtained using two functions:

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_BOOL PwElemDataMod (PWGM_HELEMENT element, PWGM_ELEMDATA *pData);
PWP_BOOL PwEnumElemDataMod (PWGM_HELEMENT element, PWGM_ENUMELEMDATA *pData);
[/sourcecode]

In addition to the Elements, each Block and Domain has an associated Condition. A Condition represents a Block’s Volume Condition or a Domain’s Boundary Condition. The Condition data is obtained using the functions listed below:

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_BOOL PwBlkCondition (PWGM_HBLOCK block, PWGM_CONDDATA *pCondData);
PWP_BOOL PwDomCondition (PWGM_HDOMAIN domain, PWGM_CONDDATA *pCondData);
[/sourcecode]

The grid model’s XYZ vertex data is obtained using the functions listed below. The specifics of a particular export format will determine which of these functions to use. See the plugin documentation for details.

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_BOOL PwVertDataMod (PWGM_HVERTEX vertex, PWGM_VERTDATA *pVertData);
PWP_BOOL PwVertIndexMod (PWGM_HVERTEX vertex, PWP_UINT32 *pIndex);
PWP_BOOL PwVertXyzVal (PWGM_HVERTEX vertex, PWGM_ENUM_XYZ which, PWGM_XYZVAL *pVal);
[/sourcecode]

Streaming Entities

The PWGM provides “streamed” access to the faces of the face centric view. They are streamed in the sense that the faces are made available to the plugin one at a time. Once the plugin has finished with a face Element, it cannot access the face Element again without restarting the stream from the beginning (unless you make local copies of the face data in the plugin).

We decided to use face streaming in the PWGM to minimize the amount of additional memory consumed during export. Pointwise does not explicitly store grid face information. As a result, to provide the face centric view, the PWGM needs to process the cell centric data and convert it to a face centric representation. Storing the full, face centric view for enumerated, random access can more than double the amount of RAM already required by the cell centric view. And, as many of our customers already know, large grid models can strain the RAM limits of many modern computers. The addition of non-streamed face Element data may exceed a computers available RAM and prevent some large grids from being exported at all!

A plugin initiates a face streaming session with a call to the PwModStreamFaces() SDK function. This function accepts a PWGM_ENUM_FACEORDER value, three callback functions, and a user-data pointer.

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_BOOL PwModStreamFaces(PWGM_HGRIDMODEL model, PWGM_ENUM_FACEORDER order,
PWGM_BEGINSTREAMCB beginCB, PWGM_FACESTREAMCB faceCB,
PWGM_ENDSTREAMCB endCB, void *userData);
[/sourcecode]

The optional user-data pointer can be set to null or it can be used to pass plugin specific runtime information to all phases of the streaming session. The user-data pointer is passed to each of the three callbacks.

The face order value controls how the faces are grouped during streaming. The supported orders are listed below.

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
typedef enum PWGM_ENUM_FACEORDER_e {
PWGM_FACEORDER_DONTCARE, // all cell faces in any order
PWGM_FACEORDER_BOUNDARYFIRST, // all cell faces in boundary, interior order
PWGM_FACEORDER_INTERIORFIRST, // all cell faces in interior, boundary order
PWGM_FACEORDER_BOUNDARYONLY, // only boundary cell faces
PWGM_FACEORDER_INTERIORONLY, // only interior cell faces
PWGM_FACEORDER_BCGROUPSFIRST, // BoundaryFirst grouped by BCs
PWGM_FACEORDER_BCGROUPSLAST, // InteriorFirst grouped by BCs
PWGM_FACEORDER_BCGROUPSONLY, // BoundaryOnly grouped by BCs
}
PWGM_ENUM_FACEORDER;
[/sourcecode]

The faces are streamed to a plugin using the three callback functions. The beginCB() callback is invoked by the PWGM once at the beginning of the streaming session. This callback receives the number of the boundary, internal and total faces that will be streamed.

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_BOOL beginCB(PWGM_BEGINSTREAM_DATA *data);

typedef struct PWGM_BEGINSTREAM_DATA_t {
PWGM_ENUM_FACEORDER order; // requested cell face stream sequence order.
PWP_UINT32 totalNumFaces; // total # of faces in model (= #Boundary + #Interior).
PWP_UINT32 numBoundaryFaces; // # faces in totalNumFaces lie on the model’s boundary.
PWP_UINT32 numInteriorFaces; // # faces in totalNumFaces that are interior to the model.
PWGM_ELEMCOUNTS counts; // model’s total element counts.
PWGM_HGRIDMODEL model; // grid model handle
void *userData; // pointer passed to PwModStreamFaces(…, void *userData)
}
PWGM_BEGINSTREAM_DATA;
[/sourcecode]

When the plugin returns from beginCB(), the PWGM begins processing the cell centric data. As faces are completed, they are passed to the faceCB() callback. The information passed to this callback includes the face type (internal or boundary), the face vertices, and the block cell Elements on either side of the face.

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_BOOL faceCB(PWGM_FACESTREAM_DATA *data);

typedef struct PWGM_FACESTREAM_DATA_t {
PWGM_HGRIDMODEL model; // grid model handle
PWP_UINT32 face; // face’s index in the model’s index space
PWGM_ELEMDATA elemData; // The face’s element data.
PWGM_ENUM_FACETYPE type; // One of the PWGM_FACETYPE_XXX types.
PWGM_FACEREF_DATA owner; // The block element that owns face.
PWGM_FACEREF_DATA neighbor; // The block element on the other side of the face. This value
// is undefined if type is PWGM_FACETYPE_BOUNDARY.
void *userData; // pointer passed to PwModStreamFaces(…, void *userData)
}
PWGM_FACESTREAM_DATA;
[/sourcecode]

After the last face is passed to faceCB(), the PWGM makes one final call to the endCB() callback.

[sourcecode language=”cpp” gutter=”true” toolbar=”false”]
PWP_BOOL endCB(PWGM_ENDSTREAM_DATA *data);

typedef struct PWGM_ENDSTREAM_DATA_t {
PWGM_HGRIDMODEL model; // grid model handle
PWP_BOOL ok; // PWP_TRUE if streaming completed successfully
void *userData; // pointer passed to PwModStreamFaces(…, void *userData)
}
PWGM_ENDSTREAM_DATA;
[/sourcecode]

The following example C++ code shows the streaming API in use.

[sourcecode language=”cpp” collapse=”true”]
/**************************************/
PWP_UINT32
beginCB(PWGM_BEGINSTREAM_DATA *data)
{
// Cast user data pointer to a CAEP_RTITEM*
CAEP_RTITEM *pRti = (CAEP_RTITEM*)data->userData;

// perform initialization here

// Set starting progress step count
 return caeuProgressBeginStep(pRti, data->totalNumFaces);
}

/**************************************/
PWP_UINT32
faceCB(PWGM_FACESTREAM_DATA *data)
{
// Cast user data pointer to a CAEP_RTITEM*
CAEP_RTITEM *pRti = (CAEP_RTITEM*)data->userData;

// process this face
if (PWGM_FACETYPE_INTERIOR == data->type) {
// do something
}
else if (PWGM_FACETYPE_BOUNDARY == data->type) {
// do something else
 }

// Increment progress
return caeuProgressIncr(pRti);
}

/**************************************/
PWP_UINT32
endCB(PWGM_ENDSTREAM_DATA *data)
{
// Cast user data pointer to a CAEP_RTITEM*
CAEP_RTITEM *pRti = (CAEP_RTITEM*)data->userData;

// perform final cleanup here

// End progress step
return caeuProgressEndStep(pRti);
}

/**************************************/
PWP_BOOL
runtimeWrite(CAEP_RTITEM *pRti, PWGM_HGRIDMODEL model,
const CAEP_WRITEINFO * /*pWriteInfo*/)
{
PWP_BOOL ret = PWP_FALSE;
if (caeuProgressInit(pRti, 2) && writePointsFile(*pRti)) {
// In this example, passing pRti as the PwModStreamFaces(…, void *userData) parameter.
// However, any implementation specific value can be used.
ret = PwModStreamFaces(model, PWGM_FACEORDER_BCGROUPSLAST, beginCB, faceCB, endCB, pRti);
}
caeuProgressEnd(pRti, ret);
return ret;
}
[/sourcecode]

That’s All Folks

That's all folks

Armed with an understanding of the unstructured grid model available within the Pointwise CAE plugin SDK, you should be able to create a plugin that adds your solver of choice to Pointwise. As always, if you have any further questions feel free to contact Pointwise support. We are here to help!

I will be covering the structured grid model in my next post. In the meantime, you can learn more about the PWGM by clicking on the button below.

Learn more about the Pointwise CAE Plugin SDK

About David Garlisch

Illini by birth, Texan by choice.
This entry was posted in Applications, Software and tagged , , , , , , , , . Bookmark the permalink.

1 Response to Creating a CAE Plugin – Understanding the Pointwise Grid Model API (Part 1)

  1. Pingback: Specifying BC points of unstructured grid in NASTRAN format -- CFD Online Discussion Forums

Leave a Reply