Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Next »

Creating Custom Applications

DWGdirect provides the facilities for creating dynamically loaded custom modules, and custom objects derived from DWGdirect classes.

Creating a Custom Module

To create a custom module, the user should create a subclass of OdRxModule, as in the following example:

class CustomObjectsModule : public OdRxModule
{
protected:
  void initApp()
  {
    // register the custom object types defined in this module
    ExCustObject::rxInit();
    ExCustEntity::rxInit();
    ExSphere::rxInit();

    // custom commands can be registered here
  }

  void uninitApp()
  {
    // unregister the custom object types defined in this module
    ExSphere::rxUninit();
    ExCustEntity::rxUninit();
    ExCustObject::rxUninit();

    // remove custom commands here
  }
};

// macro that defines the entry point for this library
ODRX_DEFINE_DYNAMIC_MODULE(CustomObjectsModule);

A custom module should contain exactly 1 subclass of OdRxModule, but can define any number of custom objects.

Naming Convention for Custom Modules

The shared library containing the subclass of OdRxModule, should be named < application name >.drx, where < module name > is the same module name that is used to define the custom object classes in this context.

Creating Client Applications

Smart Pointers

DWGdirect clients should use the supplied smart pointer classes when accessing database objects. In the above example, OdDbDatabasePtr is a smart pointer class that wraps a reference to an OdDbDatabase object. All database classes have corresponding smart pointer classes with the "Ptr" suffix attached, for example a smart pointer to an OdDbCircle is an OdDbCirclePtr. When used properly, the DWGdirect smart pointer classes guarantee the proper management of heap objects both in normal execution cases, and in cases where exceptions transfer control out of the current scope.

Accessing Database Objects

This section describes how to retrieve various types of data from a database.

Accessing System Header Variables

Header variables can be accessed directly from an instance of OdDbDatabase, for example:

pDb->setLTSCALE(1.2);
std::cout << "  LTSCALE: " << pDb->getLTSCALE() << std::endl;

In the general case, all header variable access functions are in the form OdDbDatabase::getVARNAME() and OdDbDatabase::setVARNAME().

Accessing Table Entries

Table data is retrieved by opening the table object, and then using an iterator to step through the table entries. The following function iterates through the Layers table and prints the name of each layer, assuming pDb is a valid database, and os is an output stream.


void DbDumper::dumpLayers(OdDbDatabase* pDb, STD(ostream) & os)
{
  // Layer table smart pointer, opened for read.
  OdDbLayerTablePtr pLayers = pDb->getLayerTableId().safeOpenObject();

  os << STD(endl) << pLayers->desc()->name() << STD(endl);

  // Get a new layer table iterator (as a smart pointer)
  OdDbSymbolTableIteratorPtr pIter = pLayers->newIterator();
  for (pIter->start(); !pIter->done(); pIter->step())
  {
    // Layer Record smart pointer, opened for read.
    OdDbLayerTableRecordPtr pLayer = 
      pIter->getRecordId().safeOpenObject();

    // Read access to the layer record data:
    os << "  " << pLayer->getName().c_str() << " <" << 
      pLayer->desc()->name() << ">";
    os << ", " << (pLayer->isOff() ? "Off" : "On");
    os << ", " << (pLayer->isLocked() ? "Locked" : "Unlocked");
    os << ", " << (pLayer->isDependent() ? 
      "Dep. on XRef" : "Not dep. on XRef");
    os << STD(endl);
  }
} // end DbDumper::dumpLayers

In this example, the use of smart pointers eliminates the need to explicitly close or delete the pointers returned by the calls to OdDbObjectId::safeOpenObject(). Entries from other tables can be obtained in a similar manner.

Accessing Block Entities

Entities are owned by blocks (OdDbBlockTableRecord), so a block must first be opened before its entities can be accessed. The following code iterates through all entities in all blocks of a drawing, and calls the dumpEntity() function for each entity:


void DbDumper::dumpBlocks(OdDbDatabase* pDb, STD(ostream) & os)
{
  // Open the block table
  OdDbBlockTablePtr pBlocks = 
    pDb->getBlockTableId().safeOpenObject();
  
  os << STD(endl) << "Blocks: " << STD(endl);
  // Get an iterator for the block table
  OdDbSymbolTableIteratorPtr pBlkIter = pBlocks->newIterator();
  
  // For each block in the block table
  for (pBlkIter->start(); ! pBlkIter->done(); pBlkIter->step())
  {
    // Open the block
    OdDbBlockTableRecordPtr pBlock = 
      pBlkIter->getRecordId().safeOpenObject();
    
    os << "  " << pBlock->getName().c_str() << STD(endl);

    // Get an entity iterator
    OdDbObjectIteratorPtr pEntIter = pBlock->newIterator();
    
    // For each entity in the block
    for (; !pEntIter->done(); pEntIter->step())
    {
      dumpEntity(pEntIter->objectId(), os);
    }
  }
} // end DbDumper::dumpBlocks

Accessing Objects in the Named Objects Dictionary

The starting point for accessing objects in a database is the main dictionary object. The following example prints the object hierarchy starting from the main dictionary, and recurses into any sub dictionaries that it finds (it does not traverse the entire object hierarchy).


void DbDumper::dumpObject(OdDbObjectId id, 
                          const OdString& itemName,
                          STD(ostream) & os, 
                          const OdString& padding)
{
  OdString pad(padding);
  // Open the object
  OdDbObjectPtr pObject = id.safeOpenObject();

  os << pad.c_str();
  if (!itemName.isEmpty())
  {
    os << itemName.c_str() << ", ";
  }

  // Print the object's class name.
  os << "<" << pObject->isA()->name() << ">" << STD(endl);

  // Check for specific object types.
  if (pObject->isKindOf(OdDbDictionary::desc()))
  {
    OdDbDictionaryPtr pDic = pObject;

    // Get a dictionary iterator.
    OdDbDictionaryIteratorPtr pIter = pDic->newIterator();
    pad += "  ";
    for (; !pIter->done(); pIter->next())
    {
      // Dump each item in the dictionary.
      dumpObject(pIter->objectId(), pIter->name(), os, pad);
    }
  }
  else if (pObject->isKindOf(OdDbXrecord::desc()))
  {
    OdDbXrecordPtr pXRec = pObject;
    // Following is broken for objectID's
    dumpGroupCodes(pXRec->rbChain(), os, pad + "  ");
  }
  else if (pObject->isKindOf(OdDbDimAssoc::desc()))
  {
    dumpDimAssoc(pObject, os, pad);
  }
} // end DbDumper::dumpObject

The above object dump can be initiated from the main dictionary:

dumpObject(pDb->getNamedObjectsDictionaryId(), 
           "Named Objects Dictionary", 
           pDb, 
           os, 
           "  ");

Items in a dictionary can also be retrieved by item name, using the OdDbDictionary::getAt() function. The following code segment retrieves the entry called "ACAD_LAYOUT" from the specified dictionary (if it exists):

OdDbDictionaryPtr pDic = 
  pDb->getNamedObjectsDictionaryId().openObject(OdDb::kForRead);
if (pDic)
{
  OdDbDictionaryPtr pLayoutDic = 
    pDic->getAt("ACAD_LAYOUT", OdDb::kForRead);
  if (pLayoutDic)
  {
    // Do something with the layout dictionary
  }
}

Note that the layout dictionary ID can be accessed directly from a database by calling OdDbDatabase::getLayoutDictionaryId().

Creating Objects

The base class for all objects that require runtime type identification is OdRxObject (this is the base class for all database objects, and many other auxiliary classes used by Teigha). Instances of OdRxObject can be created by calling the static OdRxClass::createObject() function. For example, the following code segment creates a dictionary object:

OdDbDictionaryPtr pDic;
pDic = OdDbDictionary::createObject();

An object created in this fashion does not belong to a database when it is first created--it must be explicitly added to a database by the client. The following code segment creates a new dictionary and an xrecord, and adds the new objects the main dictionary. As the following example demonstrates, an OdDbObjectID is assigned to an object when the object is added to a database, and this OdDbObjectID is normally returned by the function that added the object (OdDbDictionary::setAt in this case).

void DbFiller::addCustomObjects(OdDbDatabase* pDb)
{
  //Open the main dictionary
  OdDbDictionaryPtr pMain = 
    pDb->getNamedObjectsDictionaryId().safeOpenObject(OdDb::kForWrite);

  // Create the new dictionary.
  OdDbDictionaryPtr pOdtDic = OdDbDictionary::createObject();
  
  // Add new dictionary to the main dictionary.
  OdDbObjectId dicId = pMain->setAt("DWGDIRECT_OBJECTS", pOdtDic);
  
  // Create a new xrecord object.
  OdDbXrecordPtr pXRec = OdDbXrecord::createObject();

  // Add the xrecord the owning dictionary.
  OdDbObjectId xrId = pOdtDic->setAt("PROPERTIES_1", pXRec);
  
  OdResBufPtr pRb, temp;
  temp = pRb = OdResBuf::newRb(1000);
  temp->setString("Sample XRecord Data");
  
  temp = appendXDataPair(temp, 40);
  temp->setDouble(3.14159);
  
  temp = appendXDataPair(temp, 70);
  temp->setInt16(312);
  
  pXRec->setFromRbChain(pRb);
} //end DbFiller::addCustomObjects

In general, the following steps must be performed to add an object to a database:

  • Open the parent object for write (the parent is the object that will own the newly created object within the database hierarchy).

  • Create the new object.

  • Set the minimal number of properties required to add the object to the database:

    • Table entries - Must have a name before they can be added to a database.

    • Entities - Normally can be added to a database without setting any properties.

    • Objects - Normally can be added to a database without setting any properties.

  • Add the new object to its parent, getting back the new Object ID for this object. Adding the object to a database does not change its open status--it will remain in the same state (open for write) after it has been added to a database.

  • Set the remaining properties for the new object. Note that some properties can only be set on a database resident object, so as a general rule it is simpler to set all remaining properties (other than those specified in point 3 above) after the object has been added to the database.

  • Close the object and its parent. This happens implicitly when the smart pointers used to open and create the objects are destroyed, or it can be done explicitly by calling the OdSmartPtr::release() method on the appropriate smart pointer objects.

Creating New Table Objects

Table objects can be added to a database by doing the following:

  • Open the table to which the new object will be added, in OdDb::kForWrite mode.

  • Create the new table entry.

  • Set the name of the entry, and any other desired properties.

  • Add the new entry to the open table object.

  • Close the table entry and table objects (this is done automatically when the smart pointers holding references to these objects go out of scope).

For example, the following function adds a new layer to a database:

OdDbObjectId DbFiller::addLayer(OdDbDatabase* pDb, const OdString& layerName)
{
  // Add a new layer to the drawing
  OdDbLayerTablePtr pLayers;
  OdDbLayerTableRecordPtr pLayer;
  OdDbObjectId id;

  pLayers = pDb->getLayerTableId().safeOpenObject(OdDb::kForWrite);
  pLayer = OdDbLayerTableRecord::createObject();

  // Name must be set before a table object is added to a table.
  pLayer->setName(layerName);

  // Add the object to the table.
  id = pLayers->add(pLayer);

  return id;
} // end DbFiller::addLayer

Creating New Entities

Entities are owned by OdDbBlockTableRecord objects. An entity can be created and added to a block as follows:

  • Open the OdDbBlockTableRecord to which the new entity will be added, in OdDb::kForWrite mode.

  • Create the new entity.

  • Add the new entity to the open OdDbBlockTableRecord object.

  • Set properties for the newly created entity, if necessary.

  • Close the OdDbBlockTableRecord object and the new entity (this is done automatically when the smart pointers holding references to these objects go out of scope).

The following example adds an OdDbBlockReference entity to a specified block:

OdDbObjectId DbFiller::addInsert(
  OdDbDatabase* pDb,            // owning database
  OdDbBlockTableRecord* pBlock, // block to which insert will be added
  const OdDbObjectId& blockId,  // ID of block to be inserted
  double xscale,                // X scale for insertion
  double yscale)                // X scale for insertion
{
  OdDbObjectId brefId;
  
  // Create the new insert entity.
  OdDbBlockReferencePtr pBlkRef = OdDbBlockReference::createObject();
  // Add the entity to the parent block.
  brefId = pBlock->appendOdDbEntity(pBlkRef);

  // Set the remaining entity properties.
  pBlkRef->setBlockTableRecord(blockId);
  pBlkRef->setScaleFactors(OdGeScale3d(xscale, yscale, 1.0));
  pBlkRef->setPosition(OdGePoint3d(6, 6, 0));
  return brefId;
} // end DbFiller::addInsert

Other entities (circles, lines, etc.) can be added in a similar manner.

Advanced Data Extraction

This section describes advanced functionality and techniques for extracting data from a DWG/DXF file using Teigha.

Dispatching on Entity Type

Applications that process entity data from a database require a means to identify each entity by its type, and perform type-specific processing on the entity. Teigha uses a mechanism called protocal extension to provide this functionality. Protocol extension allows a client application to create a new class and associate an instance of this class with a particular class of database objects. Then this new class instance may be retrieved from a generalization of the class with which it was registered, providing an effective means of dispatching by type.

As an example, consider the protocol extension classes defined in ExProtocolExtension.h and ExProtocolExtension.cpp. The purpose of these classes is to dump the contents of the entities in a database, based on entity type. The implementation can be broken up into the following steps.

Create the Parent Dispatching Class

The following is the parent class for our entity dumpers:

class OdDbEntity_Dumper : public OdRxObject
{
public:
  ODRX_DECLARE_MEMBERS(OdDbEntity_Dumper);

  virtual void dump(OdDbEntity* pEnt, STD(ostream) &os) const;
}; // end class OdDbEntity_Dumper

This class MUST be derived from OdRxObject.

Create One or More Child Dispatching Classes

The following defines a class for dumping OdDb2dPolyline entities. It overrides the OdDbEntity_Dumper::dump() function in order to dump the data specific to this entity.

class OdDb2dPolyline_Dumper : public OdDbEntity_Dumper
{
public:

  void dump(OdDbEntity* pEnt, STD(ostream) & os) const
  {
    dumpCommonData(pEnt, os);

    OdDb2dPolylinePtr pPoly = pEnt;
    OdDbObjectIteratorPtr pIter = pPoly->vertexIterator();
    for (; !pIter->done(); pIter->step())
    {
      OdDb2dVertexPtr pVertex = pIter->entity();
      if (pVertex.get())
      {
        os << "      " << pVertex->isA()->name() << STD(endl);
      }
    }
  }
}; // end class OdDb2dPolyline_Dumper

These classes MUST be derived from the parent dispatching class, in this case OdDbEntity_Dumper.

Register the Custom Dispatching Classes

The following function registers custom dumper classes for a number of entity types:

void ExProtocolExtension::initialize()
{
  // Register OdDbEntity_Dumper with DWGdirect
  OdDbEntity_Dumper::rxInit();
  m_pDumpers = new Dumpers;
  m_pDumpers->addXs();
  
}//  end ExProtocolExtension::initialize()

And the Dumpers::addXs() functions does the following:

  void addXs()
  {
    OdDbEntity::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_entityDumper);
    OdDbRegion::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_regionDumper);
    OdDbPolyline::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_polylineDumper);
    OdDb2dPolyline::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_2dPolylineDumper);
    OdDb3dPolyline::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_3dPolylineDumper);
    OdDbPolyFaceMesh::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_polyFaceMeshDumper);
    OdDbPolygonMesh::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_polygonMesh);
    OdDbBlockReference::desc()->addX(
    OdDbEntity_Dumper::desc(), &m_blockReference);
    OdDbMInsertBlock::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_mInsertBlock);
    OdDbSpline::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_splineDumper);
    OdDbEllipse::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_ellipseDumper);
    OdDbSolid::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_solidDumper);
    OdDbTrace::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_traceDumper);
    OdDb3dSolid::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_3DSolidDumper);
    OdDbProxyEntity::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_proxyEntityDumper);
    OdDbHatch::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_hatchDumper);
    OdDbCircle::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_circleDumper);
    OdDbMText::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_mTextDumper);
    OdDbText::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_textDumper);
    OdDbMline::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_mlineDumper);
    OdDbRasterImage::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_imageDumper);
    OdDbArcAlignedText::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_arcAlignedTextDumper);
    OdDbOle2Frame::desc()->addX(
      OdDbEntity_Dumper::desc(), &m_ole2FrameDumper);
  } // end addXs

All dumpers are registered for the OdDbEntity_Dumper class, so that the dumpers associated with a particular instance of OdDbEntity can be retrieved in a consistent manner (always by retrieving the dumper registered with OdDbEntity_Dumper).

Retrieve and Call the Registered Dispatching Class

The following function retrieves the custom dispatching class associated with a particular OdDbEntity instance, and calls the virtual dump() method to display the contents of this entity.

void DbDumper::dumpEntity(OdDbObjectId id, STD(ostream) & os)
{
  // Open the entity
  OdDbEntityPtr pEnt = id.safeOpenObject();

  // Retrieve the registered protocol extension object registered 
  // for this object type.
  OdSmartPtr<OdDbEntity_Dumper> pEntDumper = pEnt;

  pEntDumper->dump(pEnt, os);
  
  dumpGroupCodes(pEnt->xData(), os, "      ");    

  if (!pEnt->extensionDictionary().isNull())
  {
    dumpObject(pEnt->extensionDictionary(), 
      "ACAD_XDICTIONARY", os, "      ");
  }
} // end DbDumper::dumpEntity

The custom dispatching object registered for this object type is retrieved by a simple assignment.

Extracting Geometry from Unsupported Entity Types

The OdDbEntity class defines a virtual OdDbEntity::worldDraw() function, which is used for vectorization. Each class derived from OdDbEntity overrides this function in order to perform custom vectorization. This function provides a convenient method for extracting the geometric representation of an entity in a generic manner.

For example, the vector data associated with an unsupported OdDbEntity descendant can be easily extracted by a client application by deriving a new class from OdGiWorldGeometry (see the OdGiWorldGeometryDumper class from OdReadEx as an example), and then passing an instance of this class to OdDbEntity::worldDraw() to receive the vector geometry and attributes from the entity. See GiWorldDrawDumper.h and GiWorldDrawDumper.cpp within the OdReadEx sample folder for the complete implemention of this class.

The following is a protocol extension class for OdDbEntity, that uses the sample OdGiWorldGeometryDumper class to print the vector information contained in an OdEntity:

void OdDbEntity_Dumper::dump(OdDbEntity* pEnt, STD(ostream) & os) const
{
  dumpCommonData(pEnt, os);
  
  os << "      " << "Entity graphics data:" << STD(endl);
  
  // Dump the graphics data of "unknown" entity
  // graphics for proxy entity are retrieved by the same way
  OdGiContextDumper ctx(pEnt->database());
  OdGiWorldDrawDumper wd(os);
  wd.setContext(&ctx);
  pEnt->worldDraw(&wd);
} // end OdDbEntity_Dumper::dump

This method can be used to extract the geometry data from any OdDbEntity type.

  • No labels