Creating Custom Objects

This section describes how to derive a custom class from OdDbEntity using specific examples of overriding virtual methods provided by the OdDbEntity class. Additionally, this section discusses overriding common entity operations, such as object snap points, grip points, and stretch points.

OdDbEntity is the base class for all database objects that have a graphical representation. The OdDbEntity class is derived from the OdDbObject class.

Deriving a Custom Entity Class

To create a custom object, derive a custom entity class from the OdDbEntity or OdDbObject class.

Declare the following:

class CFxPIEntity : public OdDbEntity { public: ODDB_DECLARE_MEMBERS(CFxPIEntity); CFxPIEntity(); virtual ~CFxPIEntity(); };

For the custom entity, define a constructor and a destructor, and declare default functions using ODDB_DECLARE_MEMBERS macros. Macros are necessary for all Teigha database objects.

The implementation for a custom entity must call the ODRX_DXF_DEFINE_MEMBERS macro, which creates implementations for the functions declared by the ODDB_DECLARE_MEMBERS macro used in the class declaration:

ODRX_DXF_DEFINE_MEMBERS(CFxPIEntity, // class name OdDbEntity, // parent class name DBOBJECT_CONSTR, // creation macro OdDb::kDHL_CURRENT, // DWG version OdDb::kMReleaseCurrent, // maintenance release version OdDbProxyEntity::kTransformAllowed | OdDbProxyEntity::kColorChangeAllowed | OdDbProxyEntity::kLayerChangeAllowed, // proxy flags PIENTITYOBJECT, // DXF name PIEntObjs|Description: PI Custom Entity Example) // Application name

Note: The application name (PIEntObjs) must match the name of the shared library that contains this object (PIEntObjs.drx).

To implement a minimal custom entity, override the following functions:

  • subWorldDraw()

  • dwgOutFields()

  • dwgInFields()

Additionally, you can implement the following functions:

  • dxfOutFields()

  • dxfInFields()

Implementing Drawing Functions

Teigha calls the subWorldDraw() and subViewportDraw() functions to display an entity. You must implement the subWorldDraw() function for any class derived from OdDbEntity class. The subViewportDraw() function is optional.

virtual bool OdDbEntity::subWorldDraw( OdGiWorldDraw *pWd ) const; virtual void OdDbEntity::subViewportDraw( OdGiViewportDraw *pVd ) const;

The subWorldDraw() function builds the portion of the entity's graphical representation that can be specified independently of any particular model-space view or paper-space viewport contexts. The subViewportDraw() function calls the view-dependent portion of the entity's graphics. If any of the entity's graphics are view-dependent, the subWorldDraw() function must return false and the subViewportDraw() function must be implemented. Conversely, if the entity has no view-dependent graphics, then the subWorldDraw() function must return true. In this case, the custom entity does not implement the subViewportDraw() function.

The subWorldDraw() function takes a pointer to an OdGiWorldDraw object. OdGiWorldDraw is a container class for the following objects:

  • OdGiWorldGeometry

  • OdGiSubEntityTraits objects.

The OdGiWorldGeometry object can be accessed from within the subWorldDraw() function by using the geometry() function. The OdGiSubEntityTraits object can be accessed using the subEntityTraits() function.

The OdGiWorldGeometry object writes vectors to refresh memory using its set of drawing primitives. A primitive is the lowest-level instruction used to draw graphical entities. The world geometry object has the following functions for drawing primitives in world coordinates:

  • Circle

  • CircularArc

  • EllipArc

  • Polyline

  • Polygon

  • Mesh

  • Shell

  • Text

  • Xline

  • Ray

Example:

The OdGiSubEntityTraits object sets graphical attribute values using the following set of traits functions:

  • Color

  • Layer

  • Line type

  • Polygon fill type

  • Selection marker.

Example:

The subViewportDraw() function takes a pointer to an OdGiViewportDraw object and builds the view-specific representation of an entity. The viewport draw object is also a container object for other objects, which include the following:

  • OdGiViewportGeometry

  • OdGiSubEntityTraits

  • OdGiViewport

The viewport geometry object provides functions for querying the viewport's transformation matrices and viewing parameters.

If you use selection markers when implementing a subWorldDraw() or subViewportDraw() function, you must first set the selection marker before making any OdGiWorldGeometry or OdGiViewportGeometry call that generates graphics primitives, including calls to the mesh() and shell() functions. For example:

If necessary, you can set the first marker to an artificial value, such as -1.

OdGi objects, such as OdGiWorldDraw and OdGiViewportDraw, should not be stored as global or static variables. Do not save copies of OdGi objects across calls to the subWorldDraw() and subViewportDraw() functions. Once these functions finish execution, the OdGi objects are no longer valid. For example, the PI entity implements:

Implementing Save and Load Functions

The Teigha C++ libraries call the following functions:

  • dwgInFields() function when a custom entity is being loaded from a DWG file.

  • dwgOutFields() function when a custom entity is being saved in a DWG file.

Both functions are also used for other purposes, such as cloning. As their use is not compulsory, it is better to implement them for an entity object.

Each function takes a pointer to a filer as its primary argument. The custom entity writes and reads data from a filer using the following functions of the filer object:

  • 'rd'-functions read data

  • 'wr'-functions write data

Writing and reading functions are classified based on types of data.

The following functions let you work with various types of data:

  • rdBool() / wrBool() - read / write a boolean value

  • rdInt8() / wrInt8() - read / write a byte

  • rdInt16() / wrInt16() - read / write a integer value of int type

  • rdInt32() / wrInt32() - read / write a longer value of long type

  • rdDouble() / wrDouble() - read / write a real value of double type

  • rdString() / wrString() - read / write a string value

  • rdPoint3d() / wrPoint3d() - read / write a 3d point of OdGePoint3d type

  • rdVector3d() / wrVector3d() - read / write a 3d vector of OdGeVector3d type

  • rdScale3d() / wrScale3d() - read / write a 3d scale of OdGeScale3d type

You must load the data in the same order in which you saved it. Before loading or saving data, perform the writing and reading functions according to the dwgInFields() or dwgOutFields() function of the base class.

Note: It is recommended to keep a version number for each custom entity.

When you implement the dwgInFields() function, before reading from the filer object, use the assertWriteEnabled() function to check whether "the object is open for writing". The assertWriteEnabled() function throws an exception if this object is not open for writing and it controls automatic undo and notification of modifications.

After changing the entity's data, call the recordGraphicsModified() function, which sets the flag indicating that the entity's geometry was changed. When you implement the dwgOutFields() function, before writing to the filer object, use the assertReadEnabled() function to check whether "the object is open for reading". The assertReadEnabled() function throws an exception if this object is not open for reading. 'In'-functions should return eOk if the reading result is successful.

For example, the PI Custom entity implements the following:

The dxfInFields() and dxfOutFields() functions are implemented in a similar way. Note that the DXF format stores data in text format which can be inconsistently located in the file. For example, a DXF file obtained from another application may not save all data. A function loading data from a DXF file may not be able to read the data as it was saved in the DXF file. Additionally, the DXF format lets you skip a line of data.

In the DXF format each data burst has a group code, which identifies data. Writing functions ('wr'-functions) set a group code for each data burst and write it in the file. Reading functions ('rd'-functions) do not know what data burst they read from the file. To identify a data burst while reading, use the nextItem() function of the OdDbDxfFiler object, which returns a group code of data. The received group code can be checked using a switch-case operator. The end of the entity's data is defined by the atEOF() function of the OdDbDxfFiler object, which returns true if this filer object is at the end of an object's data. Missing data must be initialized in the dxfInFields() function at reading time or it can be set by default before reading.

To identify a custom entity in a DXF file, it is necessary to save an entity's name, which can be determined using the desc()->name() function. Here, the desc() function returns a description of the custom entity. It is used in the dxfOutFields() function to write the entity's name as a marker of an object and in the dxfInFields() function to compare the read name with an entity's name. You can compare the names using the atSubclassData() function, which returns true if a filer object is a subclass data marker with the specified argument. It is recommended that you store a version number of the entity after its name and before its data.

For example, the PI Custom entity implements the following:

Implementing Transformation Functions

Each entity uses the subTransformBy() function to apply a 3D transformation matrix to an entity and the subGetTransformedCopy() function to create a copy of itself. The subGetTransformedCopy() function applies the supplied transformation to the newly created copy.

For example, the PI Custom entity implements:

Implementing Grip Point Functions

Custom entities have grip points that appear when the user selects an entity object with a pointing device. The subGetGripPoints() function must fill in the OdGePoint3dArray array of grip points that have been defined for a custom entity. The subMoveGripPointsAt() function performs the entity modifications that result from editing a grip point.

The entity defines its grip points and how to interpret the user-supplied action. To edit a custom entity using grip points, override the subGetGripPoints() and subMoveGripPointsAt() functions. Grip points are added to a special array using the subGetGripPoints function, with each point indicating the order in which it was added starting with zero (the first point = 0, second point = 1, third point = 2, and so on). Additional points in the array are created by the append() function of the Array object.

For example, the PI Custom entity implements the following:

The subMoveGripPointsAt function is used to modify the points of the array. Grip editing in stretch mode allows you to stretch an object by moving selected grips to new locations. The Teigha C++ libraries call the subMoveGripPointsAt() function when the user is in stretch mode. For certain entities, however, some grips move the object rather than stretch it. These grips include grip points of text objects, blocks, midpoints of lines, centers of circles, centers of ellipses, and point objects. In these cases, the subMoveGripPointsAt() function calls the subTransformBy() function. The OdDbEntity::subMoveGripPointsAt() function is used to invoke the subTransformBy() function. If the subMoveGripPointsAt() function is not overridden in the custom entity, the subTransformBy() function is called.

The subMoveGripPointsAt() function uses a switch operator to check which grip point is being edited. The first function parameter indicates the array of grip points that was created using the subGetGripPoints() function. The second function parameter indicates the array of grip indices that the user edited in stretch mode. Indices of points that have changed position are located in this array. The new position of grip points is stored in the grip points array. You can select a single grip point or multiple grip points. The following example illustrates selecting multiple grip points.

Implementing Snap Point Functions

If you want your custom entity to support entity snap modes, you must override the subGetOsnapPoints() function. The Teigha C++ libraries invoke subGetOsnapPoints() to acquire the relevant snap points for the current mode. If you do not want your entity to support snap points for a particular mode, you can filter out the snap modes that you do want to support and return eOk for the others. If multiple object snap modes are active, this function is called once for each object snap mode.

The subGetOsnapPoints() function must fill in the OdGePoint3dArray array of snap points that have been defined for the custom entity. Additional points in the array are created using the append() function on the array object. In the function is usually a switch operator, which checks the snap mode and fills in the array of snap points.

For example, the PI Custom entity implements the following:

Implementing Exploding Functions

Usually, a custom entity object consists of simple entities such as lines, circles, arcs. These simple entities are processed as a single entity. In some tasks it is necessary to explode a custom entity into more simple entities. For example, a window object can be exploded into a window sill, window frame, double glass, and vent light. Each entity defines how to explode itself into simple objects. To explode a custom entity into simple entities, you must override the subExplode() function.

The subExplode() function hands over the array of smart pointers array for entity objects. Simple entities are created using the createObject() function. This function returns the smart pointer of the object. Each simple entity class has a smart pointer type. The type name contains the name of the class and a 'Ptr' suffix. You must use the type of smart pointer that corresponds to the simple entity class.

Additional objects in the array are created using the append() function. To get a pointer of an entity, call the get() function of the smart pointer type.

A simple entity uses the setPropertiesFrom() function to copy the properties from a specified entity to this entity. You must use this function to set the default properties for a created entity.

For example, the PI Custom entity implements the following:

The subExplode() function should break the entity down into less complex entities. If the resulting entities are not native entities, the function will return eExplodeAgain.