Adding Entity UUIDs

The first step in adapting foreign modeling kernels to SMTK, which you should do before you start subclassing Session, is deciding how to assign UUIDs to entities in the foreign modeling kernel so that

  • entities can persist across SMTK modeling sessions and

  • SMTK can identify entities to the modeling kernel given only a UUID (for instance, to specify inputs to a modeling kernel operation).

There are two ways to go about storing and maintaining this bidirectional map between modeling kernel entities and SMTK UUIDs:

  1. If the modeling kernel provides an attribute resource, then the UUIDs can be stored as attributes on entities. Note that it is important to verify that attributes can be stored on all the entities you wish SMTK to be able to track. For instance, OpenCASCADE does not provide a way to store attributes on “use” records or on loops and shells. This means we must use another method if we want to preserve UUIDs assigned to these entities.

  2. If the modeling kernel provides a “stable” order for traversing model entities, the mapping can be reduced to storing one or more sequences of UUIDs in an SMTK model file. If you construct this file so that it also saves session information, it can be used to “restore” a modeling session so that the same files are loaded on the server and the UUIDs preserved.

The VTK session follows the first approach and expects UUIDs to be saved as field-data on each data object to be represented in SMTK. To accelerate lookups of UUIDs, the VTK session stores the UUID as a string value on each vtkDataObject’s information object using the SMTK_UUID_KEY key. It uses a method provided by SMTK’s VTK-based rendering support to fetch and store UUIDs so that picking can be coordinated with VTK.

smtk::common::UUID Session::uuidOfHandleObject(vtkDataObject* obj) const
{
  smtk::common::UUID uid;
  if (!obj)
  {
    return uid;
  }

  uid = vtkResourceMultiBlockSource::GetDataObjectUUID(obj->GetInformation());
  if (!uid)
  { // We have not assigned a UUID yet. Do so now.
    uid = smtk::common::UUIDGenerator::instance().random();
    vtkResourceMultiBlockSource::SetDataObjectUUID(obj->GetInformation(), uid);
  }
  return uid;
}

The advantage to the first approach is that modeling kernels with attribute resources generally provide a way to preserve attributes across modeling operations. When using the latter method, there is no robust way to track entities across modeling operations that change the topology of the model.

Adding UUIDs by either technique

Regardless of the path you take above, your session should provide public methods to map both directions. The function mapping UUIDs to foreign entities will have a return type that is specific to your modeling kernel, as will the input parameter of the inverse method that returns a UUID given a foreign entity; for our example, we’ve created a new type named EntityHandle.

/// The types of entities in a VTK "model"
enum EntityType
{
  EXO_MODEL = 0x01,    //!< An entire VTK dataset (a file).
  EXO_BLOCK = 0x02,    //!< VTK BLOCKs are groups of cells inside a MODEL.
  EXO_SIDE_SET = 0x03, //!< VTK SIDE_SETs are groups of cell boundaries in a MODEL.
  EXO_NODE_SET = 0x04, //!< VTK NODE_SETs are groups of points in a MODEL.

  EXO_BLOCKS = 0x05,    //!< A group of VTK BLOCKs.
  EXO_SIDE_SETS = 0x06, //!< A group of VTK SIDE_SETs.
  EXO_NODE_SETS = 0x07, //!< A group of VTK NODE_SETs.

  EXO_LABEL_MAP = 0x08, //!< A dataset with a label-map array
  EXO_LABEL = 0x09,     //!< A dataset representing one label in a label-map array

  EXO_INVALID = 0xff //!< The handle is invalid
};

SMTKVTKSESSION_EXPORT std::string EntityTypeNameString(EntityType etype);

/// A "handle" for a VTK entity (file, block, side set, or node set)
struct SMTKVTKSESSION_EXPORT EntityHandle
{
  int m_modelNumber{
    -1
  }; //!< An offset in the vector of models (m_models) owned by the session, whose model owns m_object.
  vtkSmartPointer<vtkDataObject> m_object; //!< The dataset being presented as this entity.
  SessionPtr m_session;                    //!< The session owning this entity.

  EntityHandle();
  EntityHandle(int emod, vtkDataObject* obj, SessionPtr sess);
  EntityHandle(
    int emod,
    vtkDataObject* obj,
    vtkDataObject* parent,
    int idxInParent,
    SessionPtr sess);

  bool isValid() const;

  EntityType entityType() const;
  std::string name() const;
  int pedigree() const;
  bool visible() const;

  int modelNumber() const { return this->m_modelNumber; }

  EntityHandle parent() const;

  template<typename T>
  T* object() const;

  template<typename T>
  T childrenAs(int depth) const
  {
    T container;
    this->appendChildrenTo(container, depth);
    return container;
  }

  template<typename T>
  void appendChildrenTo(T& container, int depth) const;

  bool operator==(const EntityHandle& other) const
  {
    return this->m_session == other.m_session && this->m_object == other.m_object &&
      this->m_modelNumber == other.m_modelNumber;
  }
  bool operator!=(const EntityHandle& other) const
  {
    return this->m_session != other.m_session || this->m_object != other.m_object ||
      this->m_modelNumber != other.m_modelNumber;
  }
};

Each EntityHandle instance stores information about a model or group that should be presented in SMTK:

  1. m_session, a pointer to the session owning the model or group;

  2. m_modelNumber, an integer offset into the session’s list of top-level models (this indicates the model that owns the data object); and

  3. m_object, a pointer to a VTK dataset holding the corresponding model geometry.

The session also holds a map to identify the parent model or group of each VTK data object since multiblock datasets in VTK do not provide a way to discover the parent of a dataset (only its children).

Adding UUIDs as attributes

Although we do not provide example code in this tutorial, it should be straightforward to add UUIDs to a modeling kernel’s attribute resource either as a 16-byte binary blob or an ASCII string per entity.

For example, if we wished to make VTK points and cells available via a session, we could store UUIDs on VTK grids as point and cell data arrays. It would be more space-efficient to store these in a 2-component vtkTypeUInt64Array (2 components for a total of 128 bits per UUID), but much easier to debug if we store UUIDs in vtkStringArray instances (one for points, one for cells).

VTK provides the concept of pedigree IDs for mapping points and cells from inputs to corresponding outputs, so (for filters that support pedigree IDs) VTK behaves much like an attribute resource in a geometric modeling kernel.

If we ever expose individual points and cells in the SMTK VTK session, we could search for point- and cell-data arrays containing UUIDs. Currently, we only search for field data specifying the UUID of a dataset as shown above.

Although not required by this example, you should be aware that you may store information about a particular session instance in an SMTK JSON file by subclassing the SessionIOJSON class.

Adding UUIDs as sequences

If you must store UUIDs in an SMTK JSON file according to some stable traversal order, then you should

  1. store the arrays in your session class in the proper order and use them to perform the lookups.

  2. write from_json and to_json functions for your resource that serialize information beyond what native file formats can accommodate, including UUIDs. The VTK session illustrates how to do this but does not currently export any session information.

Summary

You should now have a way to map a foreign entity to its SMTK UUID and vice-versa. If there is any possibility that the model could change, then you are responsible for maintaining this mapping (or at least marking it dirty and rebuilding as required).

Note that with either approach it is possible to incrementally assign UUIDs to entities as the need arises (though not very efficiently when storing UUIDs by traversal order). Future versions of SMTK will focus on incremental transcription of model entities to avoid significant waits the first time a model is read.

Now that you understand how UUIDs will be stored and related to foreign modeling kernel entities, it is possible to subclass the SMTK model session class.