Hide menu
Loading...
Searching...
No Matches
Product Structure

ModelData_Model class

The ModelData_Model class encapsulates a data structure which eventually contains entire information on the 3D model. ModelData_Model contains a product structure, or a hierarchy of elements, often called a scene graph.

The model object is used by readers and writers when converting across supported formats, for instance:

ModelData_Model aModel;
STEP_Reader aReader;
bool anIsOk = aReader.ReadFile (aSource) && aReader.Transfer (aModel);
Para_Writer aWriter;
anIsOk = aWriter.Transfer (aModel) && aWriter.WriteFile (aDest);

Scene Graph

Product structure is provided via a graph that describes hierarchical relationship between the elements.

The graph (often called a 'scene graph') supports the following element types (which are subclasses of ModelData_SceneGraphElement):

  • Part is a leaf node in a graph, corresponding to a mechanical part or a product. A part is something logically undivisible and often consists of a single body (e.g. solid body).
  • Assembly is a group which contains zero, one or more graph elements, including nested sub-assemblies.
  • Instance is a particular occurence of (or a reference to) another element (part or assembly). Allows to share elements and associate individual attributes (e.g. colors) and transformation matrices.

Example - as1 assembly

Below is an example of an assembly named "as1" (STEP file can be downloaded from here) and its graph. Assemblies are shown with the folder icon (e.g. l-bracket-assembly), instances - with the arrow icons (e.g. nut-bolt-assembly_1), parts - with the sheet-like icons (e.g. bolt).

Assembly
Scene graph of an assembly model

This example will be referred to when explaining concepts of the data model.

Scene Graph Creation

The following code demonstrates creation of a graph:

ModelData_Assembly aRoot ("as1");
aModel.AddRoot (aRoot);
...
ModelData_Assembly aNBA ("nut-bolt-assembly");
ModelData_Part aBolt ("bolt");
aNBA.AddInstance (aBolt, aBTransformation, "bolt_1");
...
ModelData_Assembly aLBA ("l-bracket-assembly");
aLBA.AddInstance (aNBA, aNBATransformation, "nut-bolt-assembly_1");
aRoot.AddInstance (aLBA, aLBATransformation, "l-bracket-assembly_1");
...

Element types

Part

Part (ModelData_Part) is a leaf node in a graph, corresponding to a mechanical part. A part is something logically undivisible (in the context of application). A part refers to representations (see Part representations) which define geometrical shape of the part. Depending on an input format the part may have precise geometrical representation (B-Rep) and/or an approximated polygonal one.

The following picture demonstrates a bolt part from the assembly as1:

Part

Assembly

Assembly (ModelData_Assembly) is a group which contains zero, one or more graph elements, including nested sub-assemblies.

The following picture demonstrates the nut-bolt-assembly from the assembly as1. The nut-bolt-assembly includes two parts - bolt and nut.

Assembly

Assembly may refer to associated properties.

Instance

Instance (ModelData_Instance) is a particular occurence of some element (part or assembly). Instances allow to share elements and to associate individual attributes (e.g. colors) and transformation matrices.

The following picture demonstrates the l-bracket-assembly, which includes a single l-bracket part (in green) and three instances of the nut-bolt-assembly, each with different transformation matrix.

Assembly with 3 instances of the same sub-assembly

Each graph element can be assigned a name, which is a Unicode string:

Base_UTF16String aName ("nut");
aPart.SetName (aName);
...
aName = anAsm.Name();
if (!aName.IsEmpty()) {
//non-empty name
}

A graph element may also refer to:

Sharing graph elements

Parts and (sub-)assemblies may be shared between assemblies. For instance, in the example above, the "nut-bolt-assembly" is used three times by the "l-bracket-assembly".

Sharing is enabled by using unique instances which refer to the same shared element and have unique transformation matrices and names, for instance:

ModelData_Assembly aLBA ("l-bracket-assembly");
aLBA.AddInstance (aNBA, aNBATransformation1, "nut-bolt-assembly_1");
aLBA.AddInstance (aNBA, aNBATransformation2, "nut-bolt-assembly_2");
aLBA.AddInstance (aNBA, aNBATransformation3, "nut-bolt-assembly_3");

Instances shall always be unique and shall never be shared.

Validity Requirements

Data model contents must meet certain requirements in order to ensure correct processing by CAD Exchanger. Key requirements are documented below:

Product Structure Requirements

Product structure must meet the following requirements:

  • Roots may be parts and assemblies only. Instances may not be roots.
  • Assembly's children are always instances, so an instance shall be created to refer to a part or a nested sub-assembly.
  • A part may have zero or one B-Rep representation and/or zero, one or more polygonal representation.
  • Bodies of B-Rep representation (inside the same part or between the parts) may not share subshapes. Each subshape must have a single parent body.

Traversal of the product structure

The product structure can be traversed using two approaches:

  • with the help of iterators;
  • with the help of visitors.

Traversal using iterators

Iterators support a Java-style interface.

The root elements of the graph can be traversed as follows:

ModelData_Model::ElementIterator anIterator (aModel);
while (anIterator.HasNext()) {
const ModelData_SceneGraphElement& aSGE = anIterator.Next();
//...
}

To traverse the assembly children the iterator can be used as follows:

const ModelData_Assembly& anAsm = ...;
ModelData_Model::ElementIterator anIterator (anAsm);
while (anIterator.HasNext()) {
const ModelData_SceneGraphElement& aSGE = anIterator.Next();
const ModelData_Instance& anInstance = static_cast<const ModelData_Instance&> (aSGE);
//...
}

Traversal using visitors

The graph implements the visitor pattern, and even more specifically the hierarchical visitor pattern.

Each graph element (part, assembly, instance) accepts a visitor which must be a subclass of an abstract class ModelData_Model::ElementVisitor with redefined methods to visit respective element type. The element invokes respective visitor's methods and (recursively) sends the visitor to nested elements. This mechanism ensures visiting of entire graph or a sub-tree starting with some particular element.

The following example demonstrates how to count types of all elements in the graph:

class ElementCounter : public ModelData_Model::ElementVisitor
{
public:
ElementCounter() : myAsmCount (0), myInstanceCount (0), myPartCount (0) {}
size_t AsmCount() const { return myAsmCount; }
size_t InstanceCount() const { return myInstanceCount; }
size_t PartCount() const { return myPartCount; }
virtual bool VisitEnter (const ModelData_Assembly& /*theAssembly*/) override { ++myAsmCount; return true; }
virtual void VisitLeave (const ModelData_Assembly& /*theAssembly*/) override {}
virtual bool VisitEnter (const ModelData_Instance& /*theInstance*/) override { ++myInstanceCount; return true; }
virtual void VisitLeave (const ModelData_Instance& /*theInstance*/) override {}
virtual void operator() (const ModelData_Part& /*thePart */) override { ++myPartCount; }
private:
size_t myAsmCount;
size_t myInstanceCount;
size_t myPartCount;
};
ElementCounter aCounter;
aSG.Accept (aCounter);
std::cout << "The graph contains " <<
aCounter.AsmCount() << " assembly(ies), " <<
aCounter.InstanceCount() << " instance(s), " <<
aCounter.PartCount() << " part(s)" << std::endl;

If the redefined VisitEnter() method returns false then the respective assembly or instance will not be visited, i.e. assembly's children or instance's referred element will be skipped.

Traversal may start at any element in the hierarchy. For instance, to count the number of children and the element itself the above visitor can be used as follows:

ModelData_SceneGraphElement& aSGE = ... ; //either part, or assembly, or instance
ElementCounter aCounter;
aSGE.Accept (aCounter);
std::cout << "The element contains " << aCounter.PartCount() << " part(s)" << std::endl;

Taking transformation matrices into account

When traversing the assembly graph which may contain multiple shared components, make sure to track transformation matrices applied to different instances along the graph in order to correctly convert local coordinates to global ones. This is typically organized using a stack that contains transformation matrices accumulated while traversing the tree.

The following example provides some excerpts of how the exploration could be organized:

class SceneGraphVisitor : public ModelData_Model::ElementVisitor
{
public:
SceneGraphVisitor()
{
ModelData_Transformation anIdentity;
myTrsfs.push (anIdentity);
}
protected:
virtual bool VisitEnter (const ModelData_Instance& theInstance) override
{
ModelData_Transformation aTrsf;
if (theInstance.HasTransformation()) {
aTrsf = theInstance.Transformation();
}
ModelData_Transformation aCumulativeTrsf = CurrentTransformation() * aTrsf;
myTrsfs.push (aCumulativeTrsf);
return true;
}
virtual void VisitLeave (const ModelData_Instance& /*theInstance*/) override
{
myTrsfs.pop();
}
const ModelData_Transformation& CurrentTransformation() const
{
return myTrsfs.top();
}
private:
std::stack<ModelData_Transformation> myTrsfs;
};

Helper Visitor classes

To reduce the amount of user code to be created, the data model offers a few helper classes:

Removal of graph elements

It's possible to remove instances from an assembly and roots from the model itself, using ModelData_Assembly::RemoveInstance and ModelData_Model::RemoveRoot. The removal should be performed outside of any process of traversal over the parent of the removed object (either using ModelData_Model::ElementIterator or using ModelData_Model::ElementVisitor). If the removal is performed during traversal, the behavior is undefined.

The recommended approach is to first traverse the model and record all the removals that are needed. Then to traverse the container of these records and actually remove the items. For example, if could remove all assembly children with name remove_me as follows:

ModelData_Assembly anAsm = /* ... */;
ModelData_Model::ElementIterator anIt (anAsm);
std::vector <ModelData_Instance> aToRemove;
for (; anIt.HasNext();) {
const auto& anInstance = static_cast <ModelData_Instance&> (anIt.Next());
if (anInstance.Name() == "remove_me") {
aToRemove.push_back (aToRemove);
}
}
for (const auto& anInstance : aToRemove) {
anAsm.RemoveInstance (anInstance);
}