CAD Exchanger SDK
Data Model

Overview

CAD Exchanger data model allows to describe:

  • Product structure: a hierarchy of parts and assemblies.
  • Part representations: B-Rep and polygonal, including multi-level of details (LODs).
  • Properties: names and meta-data of various types (numbers, strings, dates, points, etc).
  • Layers: logical groups of elements.
  • Visual styles: colors, materials, etc.
  • Validation properties: bounding box, surface area, volume, centroid (center of gravity).

Thus, the data model provides a versatile and powerful way to describe product contents.

ModelData_Model class

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

An empty model can be created as follows:

ModelData_Model aModel;
bool anIsEmpty = aModel.IsEmpty(); //true

A model contains a shared pointer to its internal implementation, so simple copying an object will just copy a shared pointer:

ModelData_Model aModel2 = aModel; //both objects will point to the same contents

The model object can be saved to and restored from a file in native CAD Exchanger format. Currently XML is used as a file type. Future versions may introduce binary format.

bool anIsOk = aModel.Save ("mydocument.xml");
ModelData_Model aNewModel;
anIsOk = aNewModel.Open ("mydocument.xml");

The model object is used by importers 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);

Product structure

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

The graph (often called scene graph) supports the following element types (which subclasses 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.

Below is an example of an assembly (named "as1") 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).

dm_assembly.png
Assembly
dm_assembly_sg.png
Scene graph of an assembly model

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) and often consists a single body (e.g. solid body).

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

dm_bolt.png
Part

A part refers to one or multiple representations used to represent the shape of the part. It may refer to associated properties and visual appearance.

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 above assembly as1. The nut-bolt-assembly includes two parts - bolt and nut.

dm_nut_bolt_assembly.png
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.

dm_l_bracket_assembly.png
Assembly with 3 instances of the same sub-assembly

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.

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::Iterator 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::Iterator 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 hirarchy. 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;

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

Attributes

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

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

Instances and parts may also have visual appearance attributes. Appearance (ModelData_Appearance) is a collection of visual styles, e.g. material and line properties, and determines how the object can be displayed in a 3D view.

ModelData_Color aColor (1.f, 0.f, 0.f); //red
ModelData_Appearance anAppearance (aColor);
aPart.SetAppearance (anAppearance);
...
anAppearance = anInstance.Appearance();
if (anAppearance && anAppearance.ToColor (aColor)) {
//the instance has a generic color or a material assigned
}

Apperance attributes may also be attached to subshapes of BRep representations (see ModelData_BRepRepresentation).

Conventions on appearances:

  • Assemblies should not have own appearances. Such appearance most likely will be ignored by any algorithm (e.g. by an exporter to some target format). If all children in the assembly must use some common appearance (e.g. be of a yellow color) then an extra instance should be created which would refer to the assembly and own the desired appearance.
  • Appearance attributes attached to a deeper element overide the appearance attributes attached to parent element. Thus, if a part has an appearance attribute it shall override any other appearance attribute attached at any instance up in the hieararchy. Likewise, an appearance attribute attached at some B-Rep face shall override any parent part's or instance's attribute.

Part representations

Representations are associated with the parts and define the shape of the part. The following representations are supported:

Each part may have zero or one B-Rep representation and zero, one or several polygonal representations. Representations must be consistent with each other.

The following picture demonstrates the nut part from the above assembly as1, with one BRep (as solid body) and one polygonal representation:

dm_nut_brep.png
B-Rep representation
dm_nut_poly.png
Polygonal representation

In the case of multiple polygonal representations they are considered determininig levels of details (LOD's), i.e. granularity or accuracy of approximation of a part. LODs must be sorted from the most accurate (fine) to the least accurate (coarse) representations.

The following example demonstrates a part with one BRep (as sheet body) and three polygonal representations:

dm_bsurf_brep.png
BRep
dm_bsurf_lod1.png
Fine LOD
dm_bsurf_lod2.png
Medium LOD
dm_bsurf_lod3.png
Coarse LOD

Polygonal representations can be either explicitly created or created from B-Rep with the help of meshers (see Meshing).

The following code snippet demonstrates creation of a part with BRep and a two polygonal representations:

ModelData_Solid aSolid = ...; //exact solid shape
ModelData_BRepRepresentation aBRep (aSolid);
//automatically generates polygonal representations from BRep
ModelData_PolyRepresentation aFinePoly (aBRep, ModelAlgo_BRepMesherParameters::Fine);
ModelData_PolyRepresentation aMediumPoly (aBRep, ModelAlgo_BRepMesherParameters::Medium);
ModelData_Part aPart (aBRep, aFinePoly, aMediumPoly, "my_part");

Semantics of LOD is application-specific. The data model only provides a mechanism to define multiple polygonal representations.

B-Rep representation contains ModelData_BodyList, which is a list of bodies (ModelData_Body), which define exact part geometry. Each ModelData_Body can be solid, sheet, wireframe or acorn (see ModelData_BodyType enumeration). For more details on B-Rep representation refer to B-Rep Representation.

Each polygonal representation contains ModelData_PolyShapeList, which is a list of vertex sets. Each vertex set (subclass of ModelData_PolyVertexSet) defines either a set of triangles (ModelData_IndexedTriangleSet), polylines (ModelData_PolyLineSet) or just a point cloud ( ModelData_PolyPointSet). For more details on polygonal representation refer to Polygonal Representation.

Traversal over the representations

The part representations can be traversed as follows:

  • with the help of direct search;
  • with the help of visitors.

The following examples demonstrate using both approaches:

ModelData_Part aPart = ...;
//find B-Rep representation
ModelData_BRepRepresentation aBRep = aPart.BRepRepresentation();
if (aBRep) {
const ModelData_BodyList& aList = aBRep.Get(); //retrieve root bodies
for (ModelData_BodyList::SizeType i = 0; i < aList.Size(); ++i) {
const ModelData_Body& aBody = aList[i];
//...
}
}
//find first polygonal representation
ModelData_PolyRepresentation aPoly = aPart.PolyRepresentation (ModelData_RM_FineLOD);
if (aPoly) {
const ModelData_PolyShapeList& aList = aPoly.Get(); //retrive root vertex sets
for (ModelData_PolyShapeList::SizeType i = 0; i < aList.Size(); ++i) {
const ModelData_PolyVertexSet& aPVSet = aList[i];
//...
}
}
class MyRepresentationVisitor : public ModelData_Part::RepresentationVisitor
{
public:
virtual void operator() (const ModelData_BRepRepresentation& theRep) override { /*...*/ }
virtual void operator() (const ModelData_PolyRepresentation& theRep) override { /*...*/ }
};
MyRepresentationVisitor aVisitor;
aPart.Accept (aVisitor);

A respective operator() will be invoked for each representation encountered in the part.

Further reading

For further details on representations refer to:

  • ref B-Rep representation details
  • ref Polygonal representation details
  • ref Creation of polygonal representation from B-Rep representation
  • ref using representation providers
  • ref Traversal of representation using masks

Properties

Properties are meta data, which can be attached to any graph element in the form of property tables. Property table (ModelData_PropertyTable) contains a list of pairs {property_name, property_value}. A property name is defined with a Unicode string (Base_UTF16String) and value can have any of the following types:

Type C++ Type Example
32 bit integer I32 25
64 bit integer I64 8589934590
float float -0.2
double double 1.34e-16
timestamp time_t 1437980561 (July 27, 2015 11:02am)
String (Ascii or Unicode) Base_UTF16String "ABC123", "© 2015"
3D point BaseGeom_Point {1.2 3.4 -5.6}
3D box BaseGeom_Box {-1.2 3.4 -5.6} {2.34 5.8 3.1}

The following example demonstrates creation and attachment of properties:

ModelData_PropertyTable aPT;
aPT.Add ("id", 25);
UTF16 aString[] = {0x00A9, 0x0032, 0x0030, 0x0031, 0x0035, 0x0000};//(c) 2015
aPT.Add ("copyright", aString);
time_t aTime;
time (&aTime);
aPT.Add ("creation time", ModelData_PropertyTable::TimeType (aTime));
aPart.AddProperties (aPT);

Traversal over properties

The properties can be traversed with the help of visitors as demonstrated in the example below:

class PropertyTableVisitor : public ModelData_PropertyTable::Visitor
{
public:
virtual void operator() (PropertyNameConstReference theName, I32 theValue) override
{
//visiting integer property...
}
virtual void operator() (PropertyNameConstReference theName, float theValue) override
{
//visiting float property...
}
...
};
ModelData_PropertyTable aPT = aSGE.Properties();
if (aPT) {
PropertyTableVisitor aVisitor;
aPT.Accept (aVisitor);
}

ModelData_PropertyTable::VoidVisitor is a convenience class which provides empty implementation for each supported value type. Thus, it allows to only define methods for required types:

class StringItemVisitor : public ModelData_PropertyTable::VoidVisitor
{
public:
virtual void operator() (PropertyNameConstReference theName, const Base_UTF16String& theValue) override
{
//visiting string property...
}
};
aPT.Accept (StringItemVisitor());

A specific property item can be retrieved using the FindProperty() item for a required value type, for example:

Base_UTF16String aValue;
if (aPT.FindProperty ("author", aValue)) {
//found the 'author' item with string value
}

ModelData_PropertyTable::HasProperty() allows to quickly check if the property item has been registered.

Validation properties

The data model allows to attach specific properties called "validation properties". These properties allow to exchange data between CAD systems in order to verify correctness of the conversion and compare properties in the sending and receiving systems. These properties include:

  • volume;
  • surface area;
  • center of gravity;
  • bounding box.

When importing from or exporting to formats which support validation properties (e.g. STEP or JT) validation properties are converted from / to specific entities as prescribed by the format.

Validation properties are stored in the property table using reserved names. These names are used to recognize and distinguish them from user-defined properties and therefore may not be used for any other properties.

The following table describes names and expected value types for the validation properties:

Validation property Reserved name String C++ Type
Surface area ModelData_PropertyTable::SurfaceAreaPropertyName() "CADEX_SURFACE_AREA" double
Volume ModelData_PropertyTable::VolumePropertyName() "CADEX_VOLUME" double
Center of gravity ModelData_PropertyTable::CentroidPropertyName() "CADEX_CENTROID" BaseGeom_Point
Bounding box ModelData_PropertyTable::BoundingBoxPropertyName() "CADEX_BOUNDING_BOX" BaseGeom_Box

The following example demonstrates how to recognize a validation property:

class PropertyTableVisitor : public ModelData_PropertyTable::Visitor
{
public:
...
virtual void operator() (const Base_UTF16String& theName, double theValue) override
{
//volume
//surface area
} else {
//general case
}
}
...
};

Validation properties must be specified in the model units (linear, square or cubic).

See also
Measurements.

Visual styles

Visual appearance of the graph element is defined by an object of the ModelData_Appearance type attached thereto. The apperance can be set using ModelData_SceneGraphElement::SetAppearance() and retrieved with ModelData_SceneGraphElement::Appearance(). By default, no appearance is attached and null appearance is returned.

ModelData_Appearance supports the following style items:

  • Material - defines graphical material.
  • Generic color - defines an RGBA color.
  • Line styles - define line properties.

Material

Material definition (ModelData_Material) is aligned with the OpenGL definition. A material contains:

  • color components: ambient, diffuse, specular, and emission colors as RGBA values;
  • shininess as float.

The following screenshot demonstrates an assembly with parts of different material settings, including transparency:

dm_material_transparency.png
Parts with different materials

The following example demonstrates creation and assignment of a material:

ModelData_Color aDiffuseColor (1.f, 0.5f, 0.25f, 1.f);
ModelData_Material aMaterial (aDiffuseColor);
aMaterial.Shininess() = 96.f;
aPart.SetAppearance (ModelData_Appearance (aMaterial));

Color

Generic color is used to define a simple RGBA color, when a more feature-rich style (e.g. material) is not available. For instance, a generic color is used when importing a model from formats which only support simple colors (e.g. Parasolid). Generic colors allow to reduce memory footprint comparing to use of materials.

When both a material and a generic color are defined the material value prevails.

The following example demonstrates creation and assignment of a color:

ModelData_Color aColor (1.f, 0.f, 0.f, 1.f);
aPart.SetAppearance (aColor);

Retrieval of visual styles

The following example demonstrates how to retrieve material or generic color as a material:

ModelData_Appearance anAppearance = aPart.Appearance();
if (anAppearance) {
ModelData_Material aMaterial;
if (anAppearance.ToMaterial (aMaterial)) {
//...
}
}

The following example demonstrates how to retrieve material or generic color as a simple color:

ModelData_Appearance anAppearance = aPart.Appearance();
if (anAppearance) {
ModelData_Color aColor;
if (anAppearance.ToColor (aColor)) {
//...
}
}

Sharing of styles

To enable sharing (i.e. instead of duplicating) of materials and colors, they should be assigned via ModelData_MaterialObject and ModelData_ColorObject respectively. They subclass ModelData_BaseObject which enable sharing and refer to ModelData_Material and ModelData_Color respectively, which are simple POD's (Plain Old Data, i.e. simple data containers).

The following example demonstrate this:

std::unordered_map<std::string, ModelData_MaterialObject> aMaterialLibrary;
aMaterialLibrary.emplace ("bronze", ModelData_MaterialObject (ModelData_Material (
ModelData_Color (0.2125f, 0.1275f, 0.054f), //ambient
ModelData_Color (0.714f, 0.4284f, 0.18144f), //diffuse
ModelData_Color (0.393548f, 0.271906f, 0.166721f), //specular
ModelData_Color (0.f, 0.f, 0.f), //emissive
25.6f //shininess
)));
...
aPart1.SetAppearance (aMaterialLibrary["bronze"]);
ModelData_MaterialObject aMat1 = aPart1.Appearance().Material();
...
aPart2.SetAppearance (aMaterialLibrary["bronze"]);
ModelData_MaterialObject aMat2 = aPart2.Appearance().Material();
assert (aMat1 == aMat2); //sharing

In the above approach materials will be shared but ModelData_Appearance objects will still be distinct. To enable sharing of appearances just store the appearance objects:

//appearance libraries
std::unordered_map<std::string, ModelData_Appearance> anAppearanceLibrary;
anAppearanceLibrary.emplace ("bronze", ModelData_MaterialObject (ModelData_Material (
ModelData_Color (0.2125f, 0.1275f, 0.054f), //ambient
ModelData_Color (0.714f, 0.4284f, 0.18144f), //diffuse
ModelData_Color (0.393548f, 0.271906f, 0.166721f), //specular
ModelData_Color (0.f, 0.f, 0.f), //emissive
25.6f //shininess
)));
...
aPart1.SetAppearance (anAppearanceLibrary["bronze"]);
ModelData_Appearance anAppearance1 = aPart1.Appearance();
...
aPart2.SetAppearance (anAppearanceLibrary["bronze"]);
ModelData_Appearance anAppearance2 = aPart2.Appearance();
assert (anAppearance1 == anAppearance2); //sharing

Layers

Layers allow to group elements into logical groups. Semantics of these groups is application-specific.

A layer may have a visibility flag which can be used by user application to show on/off objects belonging to the layer.

The following example demonstrates creation and association of a layer with graph elements:

ModelData_Layer aLayer ("invisible");
aLayer.IsVisible() = false;
...
ModelData_Assembly anAsm = ...;
aLayer.Add (anAsm);

B-Rep representation subshapes can also be added to layers:

ModelData_BRepRepresentation aBRep = aPart.BRepRepresentation();
const ModelData_Face& aFace = ...; // face from aBRep
ModelData_Layer aLayer ("faces");
aBRep.AddToLayer (aFace, aLayer);

Traversal over layers

All items in layer can be traversed using ItemVisitor:

class MyLayerVisitor : public ModelData_Layer::ItemVisitor
{
public:
void operator() (const ModelData_SceneGraphElement& theSGE) override {...};
void operator() (const ModelData_Shape& theShape, const ModelData_BRepRepresentation& theRep) override {...};
};
ModelData_Layer aLayer = ...;
MyLayerVisitor aVisitor;
aLayer.Accept (aVisitor);

Alternatively, all layers for one particular scene graph element or subshape can be accessed using iterators:

// Iterate layers of scene graph element
ModelData_Part aPart = ...;
ModelData_SceneGraphElement::LayerIterator aSGELayerIt (aPart);
while (aSGELayerIt.HasNext()) {
ModelData_Layer aLayer = aSGELayerIt.Next();
...
}
// Iterate layers of subshape
ModelData_BRepRepresentation aBRep = aPart.BRepRepresentation();
const ModelData_Face& aFace = ...; // face from aBRep
ModelData_BRepRepresentation::LayerIterator aSubshapeLayerIt (aFace, aBRep);
while (aSubshapeLayerIt.HasNext()) {
ModelData_Layer aLayer = aSubshapeLayerIt.Next();
...
}

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:

  • A graph may have zero, one or more root elements.
  • 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.