02.09.2020 • C3D Converter, C3D Modeler, C3D Toolkit

How to Traverse an Imported 3D Model with C3D Toolkit

When working with the design tree of a 3D model, our C3D Converter data exchange module operates on two primary entities. The first entity is a component, which may be a part or an assembly; the second is an instance of the component.

Another entity will be mentioned below – an element. That’s a single item which describes a shape of a body in any way – it may be a solid, a wire frame, a mesh or something else.

As a matter of the fact the C3D Converter’s main mechanism operates with these entities, and if the C3D-based application implements the corresponding interfaces, that means that developers know how to traverse the model.

In the C3D Toolkit, the entity we call a component corresponds to individual and grouped entities in CAD files. These files contain elements and instances of other components in different combinations. In addition, components typically have identifiers, names, and other information.

The other approach to the imported models implies using objects of C3D Toolkit to present hierarchical assemblies. For that purpose, C3D Converter is able to map the model design tree to objects used by the C3D Modeler geometric kernel that are subtypes of the MbItem class. In this case the imported model itself is considered as the highest-level component.

When working with C3D objects, one way of mapping is used by the ImportFrom family of functions. These functions are typically called in following manner:

#include <conv_i_converter.h>
c3d::ItemSPtr rootItem;
c3d::string_t importPath;//=...
const MbeConvResType importResult = c3d::ImportFromFile( rootItem, importPath, C3D_NULL_PTR, C3D_NULL_PTR );

In this case to traverse the model correctly users should know how to decide what entity an MbItem corresponds to – if it is a component, instance or an element of a part. The reason of possible misunderstanding is clear: the standard implementation is hidden from user.

Maybe for that reason one of the most urgent issues faced by us when importing 3D models from exchange file formats is how to extract components from models, taking into account the hierarchical structure of assemblies.

Generally, the way of mapping depends on the idea how the result model tree is going to be used. In case of C3D, the model formation algorithm is aimed at presenting the model tree so as to describe the shape of the component and the relationship between its parts using the as few objects as possible. So, the algorithm of the model tree formation tries to represent a component by a single MbItem which describes shape – an MbSolid, an MbMesh or other.

In those cases where a component cannot be represented by a single MbItem element, an object of MbAssembly type is created instead. MbAssembly types include both their own elements and instances of lower-level components. When possible, subcomponents are included in higher-level components directly, without using MbInstance.

The structure of the model can be displayed by the console with the following function:

//------------------------------------------------------------------------------
// Write the structure of the model to standard output
// ---
void PrintItemsInfoFromContent( const MbItem* item, const std::string& preStr ) { 
  if ( item != C3D_NULL_PTR )
    return;
  std::cout << preStr;
  PrintProductAttributes( *item );
  switch (item->IsA()) {
  case st_Solid:
    std::cout << "Solid " << ( static_cast<const MbSolid&>(*item).IsClosed() ? "closed " : "open " ) << std::setbase(16) << item << std::setbase(10) << std::endl;
    break;
  case st_Mesh:
    std::cout << "Polygonal object" << std::setbase(16) << item << std::setbase(10) << std::endl;
  break;
  case st_WireFrame:
    std::cout << "Wire frame" << std::setbase(16) << item << std::setbase(10) << std::endl;
    break;
  case st_Instance: {
    const MbInstance& inst = static_cast<const MbInstance&>(*item);
    std::cout << "Instance " << std::setbase(16) << inst.GetItem() << std::setbase(10) << std::endl;
    PrintItemsInfoFromContent( inst.GetItem(), preStr + "  " );
  }
  break;
  case st_Assembly: {
    std::cout << "Group of items " << std::setbase(16) << item << std::setbase(10) << std::endl;
    const MbAssembly& assm = static_cast<const MbAssembly&>(*item);
    for (size_t ind = 0, sz = assm.ItemsCount(); ind < sz; ind++) {
      PrintItemsInfoFromContent(assm.GetItem(ind), preStr + "  " );
    }
  } break;
  default:
    std::cout << "Case not implemented yet " << std::setbase(16) << item << std::setbase(10) << std::endl;
    break;
  }
}
 

In a good way, an alphanumeric identifier must be defined for each component, so that users can distinguish between each one clearly. To store information specific to components, C3D Modeler’s kernel has special objects named “product attributes.”

The following example shows how to work with two product attributes, the mandatory MbProductInfo and the optional MbPersonOrganizationInfo:

#include <attr_product.h>
 //------------------------------------------------------------------------------
// Write the product attributes to standard output
// ---
void PrintProductAttributes( const MbItem& item ) {
  AttrVector productAttr; 
  item.GetAttributes( productAttr, at_ProductAttribute, at_ProductInfo ); 
  if ( (!productAttr.empty()) && (productAttr.front() != C3D_NULL_PTR) ) { 
    const MbProductInfo& prod = *(static_cast<MbProductInfo*>(productAttr.front())); 
    string_t id, name, descr; 
    std::cout << "* MbProductInfo * " << (prod.IsAssembly() ? "Assembly" : "Part") << ToSTDstring( prod.GetId() ) << " : " << ToSTDstring( prod.GetName() ) << '\t'; 
  }
  productAttr.clear(); 
  item.GetAttributes( productAttr, at_ProductAttribute, at_PersonOrganizationInfo ); 
  if ( (!productAttr.empty()) && (productAttr.front() != C3D_NULL_PTR) ) { 
    const MbPersonOrganizationInfo& persOrg = *(static_cast<MbPersonOrganizationInfo*>(productAttr.front())); 
    std::cout << "* MbPersonOrganizationInfo * " << ToSTDstring( persOrg.NameOneLine() ) << '\t'; 
  }
}
 

Elements owned by components should not have product attributes, unlike those of MbItem objects, which correspond to parts or assemblies.

It should be noted that if an MbItem object is considered as a component, it can turn out that it has no product attributes. Such cases are best avoided. Models of that kind are usually constructed with a lack of initial data under conditions where it is necessary to keep the shape of the product and thus maintain its structure. These models can be okay from the point of view of the C3D Modeler, but are problematic for the C3D Converter.

Alexander Spivakov, Head of C3D Converter Department
Author:
Alexander Spivakov
Head of C3D Converter Department
Share
Up