To implement a custom class derived from OdDbEntity, the following input/output virtual function must be overridden:
In addition to the required I/O function, the following additional functions should be implemented if needed:
The custom object calls the dwgInFields() or dxfInFields() function when a custom entity is being loaded from a .dwg or .dxf file respectively. The dwgOutFields() or dxfOutFields() function is called when a custom entity is being saved in a .dwg or a .dxf file respectively. They are also used for other purposes, such as cloning.
virtual OdResult OdDbEntity::dwgInFields( OdDbDwgFiler* pFiler );
virtual void OdDbEntity::dwgOutFields( OdDbDwgFiler* pFiler ) const;
virtual OdResult OdDbEntity::dxfInFields( OdDbDxfFiler* filer );
virtual void OdDbEntity::dxfOutFields( OdDbDxfFiler* filer ) const;
Each I/O function takes a pointer to a filer as its primary argument. The custom entity writes data to and reads data from a filer. Reading data is performed by 'rd'-functions of the filer object. Writing data is performed by 'wr'-functions of the filer object. These functions subdivide by types of data. The following list describes some of the functions that allow you to 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
Data must be loaded in the same order in which it was saved. Before loading or saving custom entity data, the corresponding method of the base class must be called to take care of base class data. For each custom entity it is recommended that you keep a version number.
Upon implementing the dwgInFields() function, it is necessary to check whether the object is open for writing before reading from the filer object. Use the assertWriteEnabled() function, which throws an exception if this object is not open for writing, and it controls automatic undo and notification of modifications.
Upon implementing the dwgOutFields() function, it is necessary to check whether the object is open for reading before writing to the filer object. Use the assertReadEnabled() function, which throws an exception if this object is not open for reading. 'In'-functions should return the eOk if the result of reading is successful.
For example, the smiley entity implements the following:
const double kCurrentVersionNumber = 1.0;
void AsdkSmiley::dwgOutFields(OdDbDwgFiler* filer) const
{
assertReadEnabled();
// Method of base class must be performed first
OdDbEntity::dwgOutFields( filer );
filer->wrDouble( kCurrentVersionNumber );
filer->wrPoint3d( center() );
filer->wrDouble( radius() );
filer->wrVector3d( normal() );
filer->wrDouble( eyesApart() );
filer->wrDouble( eyesHeight() );
filer->wrDouble( eyeSize() );
filer->wrPoint3d( mouthLeft() );
filer->wrPoint3d( mouthBottom() );
filer->wrPoint3d( mouthRight() );
filer->wrInt32( backColor().color() );
}
OdResult AsdkSmiley::dwgInFields(OdDbDwgFiler* filer)
{
assertWriteEnabled();
// Method of base class must be performed first
OdResult res = OdDbEntity::dwgInFields( filer );
if(res != eOk) return res;
// Read version number. If this is a new unknown version, load the entity as a Proxy object.
if( filer->rdDouble() > kCurrentVersionNumber ) return eMakeMeProxy;
// Read face data
setCenter( filer->rdPoint3d() );
setRadius( filer->rdDouble() );
setNormal( filer->rdVector3d() );
// Read eyes data
setEyesApart( filer->rdDouble() );
setEyesHeight( filer->rdDouble() );
setEyeSize( filer->rdDouble() );
// Read mouth data
OdGePoint3d mouthleftpt=filer->rdPoint3d(),
mouthbottompt=filer->rdPoint3d(),
mouthrightpt=filer->rdPoint3d();
setMouth( mouthleftpt, mouthbottompt, mouthrightpt );
// Read color data
mbackcolor.setColor( filer->rdInt32() );
return eOk;
}
dwgInFields() return codes
If any other code is returned, a warning will be issued. The exact return code effects only the warning message. If the object is invalid, it can be erased in dwgInFields(). This is valid if erasing the particular object during readFile() does not break drawing integrity and does not require auditing of other objects.
dwgInFields() exceptions
If an exception is thrown from dwgInFields():
Note: During recoverFile(), audit() is called for all objects.
For example:
OdResult res = pImpl->dwgInFields(pFiler);
switch (res)
{
case eOk:
case eMakeMeProxy:
break;
default:
{
OdDbAuditInfo* pAuditInfo = database()->auditInfo();
if (pAuditInfo)
{ // File is being loaded with recoverFile()
// Exception will be handled, object erased and the error reported to AuditInfo
throw (OdError(res));
}
else
{ // File is being loaded with readFile(), warning will be issued
erase(); // If necessary
}
}
}
return res;
The dxfInFields() and dxfOutFields() functions are implemented similarly to dwgInFields() and dwgOutFields() respectively. Note that the DXF format stores data in text format which can be located inconsistently in the file. For example, a .dxf file obtained from another application may not save all of the 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 allows you to skip a line of data.
In the DXF format, each data burst has a group code, which identifies data (see also Working with Tagged 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 at the time of reading, you must 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. It occurs if the filer is at any of the following:
Missing data must be initialized in the dxfInFields() function at reading 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 class name, which can be determined using the desc()->name() function. It is used in the dxfOutFields() function to write the entity's class name as a marker of an object and in the dxfInFields() function to compare the read name with an entity's class 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 class name and before its data.
For example, the smiley entity implements the following:
void AsdkSmiley::dxfOutFields(OdDbDxfFiler *filer) const
{
assertReadEnabled();
OdDbEntity::dxfOutFields( filer ); // Method of base class must be performed first
filer->wrSubclassMarker( desc()->name() );
filer->wrDouble( OdResBuf::kDxfReal, kCurrentVersionNumber );
filer->wrPoint3d( OdResBuf::kDxfXCoord, center() );
filer->wrDouble( OdResBuf::kDxfReal+1, radius() );
if( filer->includesDefaultValues() || normal() != OdGeVector3d( 0, 0, 1 ))
{
filer->wrVector3d( OdResBuf::kDxfNormalX, normal() );
}
filer->wrDouble( OdResBuf::kDxfReal+2, eyesApart() );
filer->wrDouble( OdResBuf::kDxfReal+3, eyesHeight() );
filer->wrDouble( OdResBuf::kDxfReal+4, eyeSize() );
filer->wrPoint3d( OdResBuf::kDxfXCoord+1, mouthLeft() );
filer->wrPoint3d( OdResBuf::kDxfXCoord+2, mouthBottom() );
filer->wrPoint3d( OdResBuf::kDxfXCoord+3, mouthRight() );
}
OdResult AsdkSmiley::dxfInFields(OdDbDxfFiler *filer)
{
assertWriteEnabled();
OdResult es;
OdGePoint3d center = center(),
mouthleftpt = mouthLeft(),
mouthbottompt = mouthBottom(),
mouthrightpt = mouthRight();
if( eOk != (es = OdDbEntity::dxfInFields( filer )) ) return es;
// Check that we are at the correct subclass data
if( !filer->atSubclassData( desc()->name() )) return eBadDxfSequence;
// Read version number (must be first)
if( filer->nextItem() != OdResBuf::kDxfReal ) return eMakeMeProxy;
if( filer->rdDouble() > kCurrentVersionNumber ) return eMakeMeProxy;
// Read of smiley data
while( !filer->atEOF() )
{
switch( filer->nextItem() )
{
case OdResBuf::kDxfXCoord:
filer->rdPoint3d( center );
setCenter( center );
break;
case OdResBuf::kDxfReal+1:
setRadius( filer->rdDouble() );
break;
case OdResBuf::kDxfNormalX:
filer->rdVector3d( mnormal );
break;
case OdResBuf::kDxfReal+2:
setEyesApart( filer->rdDouble() );
break;
case OdResBuf::kDxfReal+3:
setEyesHeight( filer->rdDouble() );
break;
case OdResBuf::kDxfReal+4:
setEyeSize( filer->rdDouble() );
break;
case OdResBuf::kDxfXCoord+1:
filer->rdPoint3d( mouthleftpt );
break;
case OdResBuf::kDxfXCoord+2:
filer->rdPoint3d( mouthbottompt );
break;
case OdResBuf::kDxfXCoord+3:
filer->rdPoint3d( mouthrightpt );
break;
}
}
setMouth( mouthleftpt, mouthbottompt, mouthrightpt );
return eOk;
}
The dwgInFields()/dxfInFields() input function in general should not access any other database objects:
Most (or better, all) actions requiring access to other objects should be performed in the special composeForLoad() function.
The dwgOutFields()/dxfOutFields() output function must not modify the database (modify or create new objects). All required actions must be performed in the special decomposeForSave() function.
In addition to the required I/O function, the following additional functions should be implemented if needed:
Important: composeForLoad() / decomposeForSave() are called only during file I/O operations. But dwg/dxfIn/OutFields() are called in many other cases:
An object's data stored in a file may change. It's possible to detect an object's version by the Filer version, but it does not provide enough flexibility. A good practice is to store the object's version in a file. During file loading, all objects are converted to the current version. If the object's version stored in the file is greater than the current (known by the application), the object should be loaded as a proxy. The application doesn't know how to load it and converting to a proxy would guarantee that information is not lost.
Serious problems can arise if an object's version is not saved. Detecting how the object should be read involves checking the file version, maintenance release version and XData which is used to store the object's version.
It is strongly recommended to follow the rules below:
It's often assumed that dwgInFields() and dxfInFields() are called only for a newly created object and only are used while file loading. However, dwgInFields() may be called for Undo/Redo operations also, and dxfInFields() may be called for the entMode() operation.
Another common mistake is that arrays contained in an object are not cleared at the beginning of the functions, and later push_back() operations double the array size at each step, such as when using Undo/Redo.
One more common mistake for DXF: default for missing DXF data is different from the value initialized in the constructor. For example, the default is Standard style Id and in the constructor it is initialized as Null.
Depending on Filer type, the following optimizations are possible:
The following cases work exactly the same for kFileFiler:
pFiler->wrOdInt32(arr.size()) // Goes to "Data" stream
for(int i = 0; i < arr.size(); i++)
{
pFiler->wrSoftPointerId(arr[i]); // Goes to Id stream
}
and
for(int i = 0; i < arr.size(); i++)
{
pFiler->wrSoftPointerId(arr[i]); // Goes to Id stream
}
pFiler->wrOdInt32(arr.size()); // Goes to "Data" stream
This is extremely useful if you want to skip saving Null or erased elements to file and avoid extra looping through the array to calculate non-Null elements.
Copyright © 2002 – 2022. Open Design Alliance. All rights reserved.
|