Teigha Support of OBJ Format

Igor Egorychev

July 26, 2018

OBJ is a file format developed by Wavefront that is commonly used to store 3D model geometric definitions. It is mainly used as an exchange format between different 3D applications and has become one of de-facto standards for model interoperability. Many 3D model libraries provide an option to download their models in this neutral format.

Models stored in .obj files can be enriched with detailed specifications of materials in .mtl files, which are referenced from .obj files, so exported models will not lose their views after converting to OBJ. Materials are assigned to faces, and .mtl files are always separate from .obj files, so one .mtl file can be referenced from many .obj files. Or, if materials aren’t needed, .mtl files can be simply deleted. The ability to import .obj files using Teigha also gives users access to a whole set of models from popular web-based 3D model libraries (or closed libraries from manufacturers) which can be quickly used inside their drawings as additional/decorative design elements, for example.

OBJ is an open text format, and the structure of the file isn’t hard-fixed. Several .obj variations and even dialects can be found on the web. Teigha tries to support all found deviations from the common specification when reading and parsing files.

Using the module in Teigha applications

The OBJ import/export functionality is implemented inside of the OBJToolkit project, which is part of the Components library found in the Components/OBJToolkit directory. The path in the project solution is the same. A single header file OBJToolkit.h is available for Teigha users; it contains all interfaces, variables and definitions to start working with .obj and .mtl files. The default .tx module name OdObjToolkitModuleName (“OBJToolkit.tx”) is defined inside the Kernel\Include\OdModuleNames.h header and is available by including this header. However, loading of the module can be performed by calling the loadObjToolkitModule() function which is also implemented in OBJToolkit.h. Here is the fastest way to start supporting .obj in your application:

OdObjToolkitModulePtr module = loadObjToolkitModule();

The main module’s exported OdObjToolkitModule class has several methods:

  • OdObjImportPtr createObjImporter() — Creates an object that is an implementation of an .obj file loader in the OdObjDb structure (keeps imported geometric data) and related OdMtlDb (stores material definitions assigned with the imported .obj file).
  • OdObjExportPtr createObjExporter() — Creates an object that can save an assigned OdObjDb structure into the .obj file.
  • OdMtlImportPtr createMtlImporter() — Returns a pointer to the created object that can load an .mtl file with materials information into OdMtlDb.
  • OdMtlExportPtr createMtlExporter() — Creates and returns a pointer to the object that exports an assigned OdMtlDb into an .mtl file.
  • OdResult exportObj(OdDbBaseDatabase *pDb, const OdGiDrawable *pEntity, const OdString &fileName, OdObjExportOptions *options = NULL) — Exports a defined OdGiDrawable object into an .obj file. This method is used in the ExCustObjs sample command “OBJExport” which can be called from OdaMfcApp after loading the ExCustObjs.tx module (Drawings/Examples).
  • OdResult exportObj(OdDbBaseDatabase *pDb, const OdString &fileName, OdObjExportOptions *options = NULL) — Exports a defined view into an .obj file. This method is also used to export a view from Teigha Visualize Viewer.

As soon as an .obj file is imported into OdObjDb, the following structure can be used to access the imported entities:

  • Grouping is performed by the tag ‘g’ (group) of the .obj syntax. Each group can keep objects such as point sets, polylines, curves (only b-spline curves are supported currently) and faces. A point set can be used to define point clouds, for example.
  • Each face is a separate entity from the point of view of the OBJ format, so users can access each face separately using OdObjEntityIterator, which is created when the skipFace parameter is equal to false.

Functionality that collects all faces of a group in a single shell to get a complex mesh model is also implemented in the getGroupShell method of the OdObjDb class.

In short, the hierarchy for an .obj file object model in Teigha can be described as follows:

  • OdObjDb keeps a set of named groups.
  • Each group keeps several objects (point sets, polylines, curves, faces).
  • A single shell from all faces of the group can be created using the getGroupShell method of OdObjDb.

Here is sample code of importing an .obj file and walking through all the imported groups and their entities (similar code can be found in the Components/Visualize/Examples/Obj2Visualize project):

OdObjImportPtr pObjImporter = OBJToolkit::createObjImporter();
OdResult res = pObjImporter->importFile(filePath);
OdObjDbPtr pObjDb = pObjImporter->getObjDb();
OdString modelName = pObjImporter->getDrawingName();

OdObjGroupIteratorPtr itGroups = pObjDb->createGroupsIterator();
while (itGroups->done() == false)
{
  OdObjGroupPtr pGroup = itGroups->element();
  if (pGroup.isNull() == false && pGroup->isEmpty() == false)
  {
      if (eOk == pObjDb->getGroupShell(pGroup, vertices, texCoo, normals, faces, faceMtls) && faces.size() > 0)
      {
	// process shell data
      }

      // and walking through all other entities in the group
      OdObjEntityIteratorPtr it = pGroup->createEntitiesIterator();
      while (it->done() == false)
      {
        const OdObjGeomEntity *pEntity = it->element();
        const eOBJEntType entType = pEntity->type();
        switch (entType)
        {
        case eOBJEntPoints:
          {
            if (!pEntity->m_vind.isEmpty())
            {
              // process set of points
            }
          }
          break;

        case eOBJEntLine:
          {
            if (!pEntity->m_vind.isEmpty())
            {
                // process polyline
            }
          }
          break;
        case eOBJEntCurve:
        {
          // process curve
        }
        break;
        }
      it->step();
    }
  }

  itGroups->step();
}

Everything is quite simple with OBJ!