Tuesday, March 01, 2005

Class 4 - Object Lifecycle

Now you are able to create a minimum ObjectARX project we will continue the course with some additional concepts.

As I have mentioned before, AutoCAD's database is well organized to allow simple and direct manipulation of its objects. Generally we have two basic types: Containers and Objects.

Containers are special objects that provide a simple and efficient mechanism to store, edit and persist objects or collections. They are optimized to allow quick access with minimum overhead. Each type of object has an appropriate container where you should always store your object. There are several containers inside AutoCAD's database but some of the most common are: LayerTable, LinetypeTable and BlockTable. Each container class has standard access methods and most of them also offer a companion class to iterate through its items. Every time you create an object and would like to store it inside AutoCAD's database you need to follow its container protocol to store and persist is as well.

In other hand, Objects (including entities) are the most basic types and represent each element inside AutoCAD. They are implemented through specific classes with standard and specific methods. Some of them could be derived inside your application to allow customization.
Every database resident object has an exclusive identification called ObjectId. This identification is the "name" of each object inside database and it is used to reference, open and manipulate objects.

This could be a difficult concept for those who are fluent in standard C++ language because inside ObjectARX you don't delete a pointer to a database resident object. What? Yes, this is a little bit strange but AutoCAD has several reasons to do that including performance, memory management and other aspects.

So, how can I manipulate objects?

Don't panic. You just need to keep in mind some basic but essential rules:
1) Database resident objects should never be deleted even you have erased them!

2) If you have allocated an object but did not added it to database yet, go ahead...delete the pointer!

3) If you need to get a pointer to an object to manipulate it, acquire its ObjectId and use the appropriate container method to get its pointer. That's it? NO. You need to CLOSE the objects just after you finish to use it. Call the close() method or end its transaction to inform AutoCAD that you are done! (DON'T FORGET THIS ONE BECAUSE AutoCAD WILL TERMINATE)

Most of bugs you will face at your first application will be have something to do with the above rules. Trust me!

Object Ownership and Relationship

Objects can refer each other using their ObjectId. This can be an ownership relation or just a relationship. If you think about a layer you will understand what is involved with this concept. The LayerTable container owns its records which are objects (Layers in this case). Each Layer ObjectId is referred inside each entity. Exactly due that you can't remove a Layer from a DWG file until all entities that uses this layer are erased or has its associated layer changed.

There are several examples of ownerships and relationships inside AutoCAD. During our course you will get this concept easily when we have to manipulate basic objects.

Creating a Layer

On previous classes I have presented some code fragment to explain how to create simple entities. Now I will present a simple code to create a Layer just to allow you to feel how much protocol is involved in such operation:

AcDbLayerTable* pLayerTbl = NULL;
// Get the current Database
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();

// Get the LayerTable for write because we will create a new entry
pDB->getSymbolTable(pLayerTbl,AcDb::kForWrite);

// Check if the layer is already there
if (!pLayerTbl->has(_T("MYLAYER"))) {

// Instantiate a new object and set its properties
AcDbLayerTableRecord *pLayerTblRcd = new AcDbLayerTableRecord;
pLayerTblRcd->setName(_T("MYLAYER"));
pLayerTblRcd->setIsFrozen(0); // layer set to THAWED
pLayerTblRcd->setIsOff(0); // layer set to ON
pLayerTblRcd->setIsLocked(0); // layer un-locked
AcCmColor color;
color.setColorIndex(7); // set layer color to white
pLayerTblRcd->setColor(color);

// Now, add the new layer to its container
pLayerTbl->add(pLayerTblRcd);

// Close the new layer (DON'T DELETE IT)
pLayerTblRcd->close();

// Close the container
pLayerTbl->close();
} else {
// If our layer is already there, just close the container and continue
pLayerTbl->close();
acutPrintf(_T("\nMYLAYER already exists"));
}

14 comments :

Anonymous said...

I hope you quickly follow this up with a section on smart pointers - you shouldn't have to explicitly call close() much in production code.

Fernando Malard said...

Hi Daniel,

Yes, smart pointer are pretty handy. One problem is that you can't choose the best moment to call close() method because it does this automatically. Most of times you will need to forward your pointer to other functions or modules and this will not work with smart pointers. I really prefer to use them just when accessing some COM Server because they will do the Release() automatically.

In fact, the best method to manipulate objects are Transactions which I will present soon. I personaly only use Open/Close approach when dealing with light procedures and reduced program flow.

I would like to present smart pointers on this course but I'm afraid it will require extra C++ background (like COM and template class concepts) which I prefer to avoid due the target audience.

Regards.

Anonymous said...

If you need precise control over when close() is called, put the smart pointer in another smart pointer!

std::auto_ptr AcDbLayerTablePointer pLayerTbl(new AcDbLayerTablePointer());
AcDbLayerTable* pLayerTbl_ = NULL;
VERIFY( pDB->getSymbolTable(pLayerTbl_, AcDb::kForWrite) == Acad::eOk );
(*pLayerTbl).acquire(pLayerTbl_);
(*pLayerTbl)->has("asdf");
pLayerTbl.reset(); // force call to close()

The syntax is a ugly, but usually you don't need to control the call to close() that carefully. But better some ugly syntax rather than forgetting to call close() (or delete).

The object() method on AcDbObjectPointerBase will let you get the raw pointer to pass to other functions. Or, you can pass the smart pointer itself by reference.

As you can see from the code above, all of the template stuff is hidden; you can just use the smart pointer. And it doesn't require any knowledge of COM either.

Clearly transactions are a better solution than open/close.

Fernando Malard said...

Hi Daniel,

Yes, you got the point!
The most important fact is not to forget an object opened. It doesn't matter if you use smart pointers, plain Open/Close methods or even transactions. If you follow simple code flow rules you will avoid trouble.

I disagree with you that smart pointers are simple to beginners understand. Plain ObjectARX is very difficult to learn by itself using standard functions. So you can imagine how complex will be to pass the essence of ObjectARX if I start to insert texts about other subjects.

Another problem is that all ObjectARX samples don't use smart pointers. So if I develop the course based on smart pointers users will face problems when dealing with those samples.

I have plans to release an Advanced ObjectARX course latter and I will present much more complex concepts and features. I will definitely consider include smart pointers on its contents.

Thank you again for your comments.
Cheers!

Anonymous said...

The important thing is to use techniques to ENSURE that close() is called. Explicitly calling close() on a raw object should be avoided if at all possible.

This is 2005: any competent C++ programmer needs to have a basic understanding of things like smart pointers.

Simple sample code can often be the starting point for more complex applications. As such, it's important that even "sample" code demonstrate good practices.

Agnel CJ Kurian said...

Nah! Better keep the smart pointer stuff out of this. Cool as it is.

Anonymous said...

when error code C2664 appears you have to replace lines with a string with _T(sting).
this because of the unicode conversion or something.

this line didn't work for me,

acutPrintf("\nMYLAYER already exists");

this line workes fine.

acutPrintf(_T("\nMYLAYER already exists"));

greetings Ivo Lafeber

Fernando Malard said...

Hello Ivo,

Yes, all the Blog's code are not UNICODE aware once they were made for ObjectARX 2004.

The major issue will be with strings as you said.

Regards.

jyothi said...

Hi Fernando..

Iam trying to get the system time and date with object ARX. Could u pls help me in this .

Thanks
Jyothi

Fernando Malard said...

Hi Jyothi,

Take a look at COleDateTime class.
To get the current datetime, do the following:

COleDateTime Date = COleDateTime::GetCurrentTime()

Another option is through CTime:

CString sToday;
CTime dtDateTime = CTime::GetCurrentTime();
sToday.Format(_T("%i_%.*i_%.*i"), dtDateTime.GetYear(), 2, dtDateTime.GetMonth(), 2, dtDateTime.GetDay());

Regards,

Unknown said...

Hi,

Im begginer for objectarx coding. I need some help from you.
How can i retrieve the data, if there are line or circle in .dwg file so, if a user select an entity of line. That information of a line i should retrieve through coding in objectarx. Please find a solution for my task ASAP.

Thanks in Advance.

Fernando Malard said...

Hi Sandhya,

Please take a look at this page:

http://arxdummies.blogspot.com.br/2005/03/class-5-object-management.html

Also, check this ObjectARX SDK sample: C:\ObjectARX 2015\samples\database\ents_dg

Keep in mind that every entity derives from AcDbEntity thus basic properties like Color, Layer, etc. are stored at this level but specific information is stored at the leaf class like AcDbCircle, AcDbLine, AcDbText, etc.

I would suggest you to read through SDK documentation and get more information about these classes.

Regards,

Unknown said...

hii fernando,
Iam trying to read bulge of a polyline ..
but there is no function to read a bulge in AcDbpolyline class.
can u suggest any api.

Fernando Malard said...

Karishma,

The bulge is a vertex property and can be retrieved by traversing the Polyline nodes and by calling getBulgeAt() method passing in the vertex index. The Polyline needs to be opened for read in this case.

Regards.