Exchange Toolkit
Product Traversal

Table of Contents

The Challenge

The Exchange data model is complex. It captures details about a model file such as assembly organization, attribute inheritance, PMI, geometry and tessellation. As a result, the code required to traverse the data model can be burdensome. Figure 1 illustrates a portion of this complexity as a UML diagram with recursive containers.

Figure 1. UML diagram of a portion of the Exchange data model

A direct approach for navigating the data structures to get to an object containing the information of interest typically involves writing one function per object type, with each implementation iterating over a child container and calling appropriate functions to traverse deeper into the hierarchy. This has the downside of complexity and function implementations that are just slight variations from one another, giving the feeling of code duplication. Another possible implementation is to employ the Visitor Design Pattern. This approach has a tendency of being overly complex and requires object inheritance, virtual function calls and a bloated interface if it is to cover the entirety of the Exchange data model.

The Exchange Toolkit solves this problem in a much simpler way.

Concept: Instance Path

The Exchange Toolkit addresses this challenge by first introducing the concept of an ts3d::EntityArray. This is nothing more than a typedef of a standard ordered container of A3DEntity*. An ordered container of generic "base class" pointers allows us to represent a particular path through the model's hierarchy. In the context of a path through the hierarchy, the alias ts3d::InstancePath is used.

namespace ts3d {
using EntityArray = std::vector<A3DEntity*>;
using EntitySet = std::set<A3DEntity*>;
using InstancePathArray = std::vector<InstancePath>;

Why is an InstancePath useful?

Concept: Leaf Node

Throughought the Exchange Toolkit, you may encounter the term "leaf node". This simply refers to the last object in an instance path.

Traversal Solution

The Exchange Toolkit provides just three functions for easily traversing the entirety of the Exchange data model. They are:

  1. ts3d::getLeafInstances( A3DEntity *owner, A3DEEntityType const &leaf_type )

    This function is used to obtain instance paths from the owner to all child objects of the type specified by leaf_type. The result is a ts3d::InstancePathArray containing unique paths. The leaf entity object may appear multiple times via differing paths.

    The type of the owner object is not important. The traversal algorithm will begin at whatever level of the hierarchy you need.

    As an example of how this can be used, imagine you'd like to know the total number of parts in a model file. This would look something like this:

    auto const all_parts = ts3d::getLeafInstances( loader.m_psModelFile, kA3DTypeAsmPartDefinition );
    std::cout << "This file contains " << all_parts.size() << " parts." << std::endl;

    This count would include all instances of shared parts.

  2. ts3d::getUniqueLeafEntities( A3DEntity *owner, A3DEEntityType const &leaf_type );

    This function is used to obtain a set of unique child objects of type leaf_type. The result is a ts3d::EntitySet containing all unique leaf entities. This function does not provide any information about how the hierarchy was traversed to arrive at these leaf objects.

    As an example of how this can be used, here is a snippet from the attrib example. In this code, brep_model is a ts3d::InstancePath containing a leaf object of type A3DRiBrepModel.

    auto const faces = ts3d::getUniqueLeafEntities( brep_model.back(), kA3DTypeTopoFace );
    for( auto const face : faces ) {
    // add face attributes
    ts3d::attachFaceAttributes( face, scale );

    Internally, the B-Rep model is traversed and all A3DTopoFace objects are gathered and returned in an unordered set. In this sample, we don't need to know the path to each A3DTopoFace in order to attach attributes, so this variation of the function is appropriate.

  3. ts3d::getUniqueLeafEntities( A3DEntity *owner, A3DEEntityType const &leaf_type, InstancePathMap &instance_path_map )

    This overloaded variation can be used when you need a collection of unique child objects of a particular type and you want the instance paths for each occurrence. The final parameter instance_path_map is used with each entity from the returned set as key values. The lookup returns an array of instance paths indicating the unique occurrences of the child object. The following code snippet from the bom example shows how to easily print a bill of materials.

    // An instance path map will be used to count the number of part occurrences
    ts3d::InstancePathMap instance_paths;
    // Obtain a set of unique part definition children
    auto const part_definitions = ts3d::getUniqueLeafEntities( loader.m_psModelFile, kA3DTypeAsmPartDefinition, instance_paths );
    // Iterator over each unique part
    for( auto part_definition : part_definitions ) {
    // Get the part's name or owner's name if empty
    ts3d::Instance const part_instance( { part_definition } );
    auto const part_name = part_instance.getName().empty() ? "<unknown>" : part_instance.getName();
    // Print the part name and number of occurrences
    std::cout << "\"" << part_name << "\": "
    << "(" << instance_paths[part_definition].size()
    << " instance"
    << (1 != instance_paths[part_definition].size() ? "s)" : ")")
    << std::endl;

    Using samples/data/catiaV5/CV5_Micro_Engine/_micro engine.CATProduct as input, this code writes the following lines to standard out:

    "SCREW TOP": (4 instances)
    "CARBURETOR": (1 instance)
    "SCREW BACK": (4 instances)
    "HOUSING BACK": (1 instance)
    "BEARING PR UP": (1 instance)
    "PUSH ROD": (1 instance)
    "AXE": (1 instance)
    "BEARING PR DW": (1 instance)
    "CRANKSHAFT": (1 instance)
    "PISTON": (1 instance)
    "CYLINDER LINER": (1 instance)
    "HOUSING TOP": (1 instance)
    "HOUSING": (1 instance)
    "HOUSING FRONT": (1 instance)
    "BEARING CS": (1 instance)