Exchange Toolkit
Data Access

Table of Contents

The Challenge

If you are familiar with the Exchange API, then you have almost certainly seen this pattern before:

// Assume you have some product occurrence and want to get its contents...
A3DAsmProductOccurrence *po = getSomePO();
// First you must declare a struct to hold data
A3DAsmProductOccurrenceData po_data;
// Second, you must initialize the struct so it can be used
A3D_INITIALIZE_DATA(A3DAsmProductOccurrenceData, po_data);
// Then you can retrieve the data
A3DAsmProductOccurrenceGet( po, &po_data );
// And now you can use the data
partProcessingAlgorithm( po_data.m_pPart );
// Finally, you must free any dynamic resources by calling
A3DAsmProductOccurrenceGet( nullptr, &po_data );

Most programmers would agree that this is verbose and error prone.

Concept: Wrapper Macros

The Exchange Toolkit provides a solution to this pattern by exposing the macro A3D_HELPERS( A3D_VOID_TYPE ). This macro is expanded for many of the Exchange data types which results in a set of structs that can be used to read data from the Exchange API in a more concise way.

In order to make the concept more clear let's take a look at the following code snippet, which is similar to the code expansion of the macro A3D_HELPERS( A3DAsmModelFile ).

namespace ts3d {
struct A3DAsmModelFileWrapper {
// Acquire resources on creation
A3DAsmModelFileWrapper( A3DAsmModelFile *p ) {
A3D_INITIALIZE_DATA( A3DAsmModelFileData, _d );
if( p ) {
A3DAsmModelFileGet( p, &_d );
}
}
// Free resources on destroy
~A3DAsmModelFileWrapper( A3DAsmModelFile *p ) {
A3DAsmModelFileGet( nullptr, &_d );
}
// Concise data access
A3DAsmModelFileData *operator->( void ) {
return &_d;
}
// Convenience function for reassignment
void reset( A3DAsmModelFile *p ) {
A3DAsmModelFileGet(nullptr, &_d );
if( p ) {
A3DAsmModelFileGet( p, &_d );
}
}
A3DAsmModelFileData _d;
};
}

With this implementation, we now have easy access to the data from the Exchange API. If we rewrite the doSomething code from above, it might look like this:

// Assume you have some product occurrence and want to get its contents...
A3DAsmProductOccurrence *po = getSomePO();
// Declare the wrapper for concise access to the Exchange Data
// And now you can use the data
partProcessingAlgorithm( d->m_pPart );

Or, if you want to go crazy with concise code, simply write:

// A little ugly, but could be one line!
partProcessingAlgorithm( ts3d::A3DAsmProductOccurrentWrapper( getSomePO() )->m_pPart );
See also
Data Access Wrappers

Concept: Instance

The class ts3d::Instance provides additional functionality for computing "net attributes" that are based on a specific ts3d::InstancePath.

To understand this better, lets consider the specific example of a part that is instanced multple times in a model. A bolt is a great example of this because one bolt size is often used throughout a model, with each instance having a different world position.

The world position of each bolt instance is determined by the nodes of the assembly tree that contain it. Each node of the assembly tree imparts a local transformation, and once reaching the leaf node of the product structure, you arrive at a part instance of the bolt with some final accumulated transform.

Without the help of the Exchange Toolkit, one would have to write recursive code to traverse the assembly hierarchy. At each node of the traversal, you must obtain the node's transform and accumulate the transform with those previously encountered. To further complicate this task, A3DAsmProductOccurrence objects store their transform in two different ways.

The ts3d::Instance class makes this task easier when used with the function ts3d::getNetMatrix(). Similarly, there are several attributes whose final (or "net") values are determined by the path taken through the assembly hierarchy to arrive at a particular leaf node.

Coupling this class with the Traversal functionality from exchange, you can easily obtain net attributes from individual parts by writing the following code:

auto const parts = ts3d::getLeafInstances( loader.m_psModelFile, kA3DTypeAsmPartDefinition );
for( auto const part_path : parts ) {
ts3d::Instance part_instance( part_path );
std::cout << "Net Matrix: " << ts3d::getNetMatrix( part_instance );
}
Note
ts3d::getNetMatrix() returns a ts3d::MatrixType, which is defined in the Eigen Bridge.

Arrays

The Exchange API often contains C-style arrays. The struct containing the array will typically have two data members, one for the pointer and another for the size of the array.

For convenience, the Exchange Toolkit provides the function ts3d::toVector for converting these arrays to std::vector objects.

As an example, the traditional implementation:

// Declare
A3DAsmProductOccurrenceData d;
// Initialize
A3D_INITIALIZE_DATA( A3DAsmProductOccurrenceData, d );
// Get
A3DAsmProductOccurrenceGet( po, &d );
// C-style Loop
for( auto idx = 0u; idx < d.m_uiPOccurrenceSize; ++idx ) {
doSomethingWithChild( d.m_ppPOccurrences[idx] );
}
// Free
A3DAsmProductOccurrenceGet( nullptr, &d );

Can be rewritten as:

// Use the data access wrapper
// C++ style loop
for( auto const child_po : ts3d::toVector( d->m_ppPOccurrences, d->m_uiPOccurrencesSize ) ) {
doSomethingWithChild( child_po );
}

Tessellation

The Exchange API provides access to a tessellated representation of data in the form of triangles, triangle strips and triangle fans. There are variations of the form this data takes depending on the presence of per-vertex normal vectors, or texture coordinates. This variation results in about 12 different cases for parsing the tessellation.

Many tessellation-based use cases are concerned with triangle primitives only. Fans and strips must be decomposed, along with normal vectors and texture coordinates. This simplification of the Exchange data set can lead to confusion and is indeed error prone.

To address this challenge, the Exchange Toolkit provides some tools and objects making access to an index mesh much easier.

The class ts3d::RepresentationItemInstance is the entry point to obtaining the simplified tessellation data. Since represenation items contain the tessellation, this class requires a ts3d::InstancePath with a leaf node type that is a represenation item. Once constructed, used the method ts3d::RepresentationItemInstance::getTessellation() to obtain the data.

Faces

The Exchange toolkit provides an abstraction that simplifies access to the triangles that make up a topological face. The class that provides this functionality is ts3d::Tess3DInstance. This is a concrete implementation of ts3d::TessBaseInstance returned from the call to ts3d::RepresentationItemInstance::getTessellation(). From this object you can obtain a ts3d::TessFaceDataHelper object for each face.

Refer to this snippet of code extracted from examples/obj/main.cpp as an example of how this functionality can be used. This code writes the tessellation data to an OBJ file. Note the use of ts3d::Instance::getNetShow() and ts3d::getNetMatrix().

auto const ri_instances = ts3d::getLeafInstances( loader.m_psModelFile, kA3DTypeRiRepresentationItem );
for( auto ri_instance : ri_instances ) {
ts3d::RepresentationItemInstance const ri( ri_instance );
if( !ri.Instance::getNetShow() ) {
continue;
}
auto const tess3d = std::dynamic_pointer_cast<ts3d::Tess3DInstance>( ri.getTessellation() );
if( nullptr == tess3d ) {
continue;
}
auto const name = ri.getName();
obj_file << "o " << name << std::endl;
auto const net_style = ri.Instance::getNetStyle();
auto const mtl = getMaterial( net_style );
if( !mtl.empty() ) {
obj_file << "usemtl " << mtl << std::endl;
}
auto const net_matrix = ts3d::getNetMatrix( ri );
auto const exchange_coords = tess3d->coords();
auto const n_coords = tess3d->coordsSize();
for( auto idx = 0u; idx < n_coords; idx += 3 ) {
auto v = net_matrix * ts3d::VectorType( exchange_coords[idx], exchange_coords[idx+1], exchange_coords[idx+2], 1. );
obj_file << "v " << v(0) << " " << v(1) << " " << v(2) << std::endl;
}
auto const exchange_normals = tess3d->normals();
auto const n_normals = tess3d->normalsSize();
for( auto idx = 0u; idx < n_normals; idx += 3 ) {
auto n = net_matrix * ts3d::VectorType( exchange_normals[idx], exchange_normals[idx+1], exchange_normals[idx+2], 0. );
obj_file << "vn " << n(0) << " " << n(1) << " " << n(2) << std::endl;
}
for( auto idx = 0u; idx < tess3d->faceSize(); ++idx ) {
auto const face_mesh = tess3d->getIndexMeshForFace( idx );
auto const n_vertices = face_mesh.vertices().size();
for(auto idx = 0u; idx < n_vertices; ++idx ) {
if( 0 == idx % 3 ) {
obj_file << std::endl << "f";
}
obj_file << " -" << (n_coords - face_mesh.vertices()[idx])/3 << "//-" << (n_normals - face_mesh.normals()[idx])/3;
}
}
obj_file << std::endl;
}

Edges

ts3d::TessFaceDataHelper provides a method for obtaining the loops of edges associated with the topological face it is associated with. ts3d::TessFaceDataHelper::loops() returns an ordered collection of ts3d::TessFaceDataHelper::TessLoop objects. Each TessLoop contains an ordered collection of ts3d::TessFaceDataHelper::TessEdge objects. Each TessEdge contains a visibility flag, and an order collection of index values for the points of the polyline representing the edge.

See also
Comparing tessellation to B-Rep

Wires

Todo:
Implement and test wire tessellation objects
Note
This implementation is incomplete!