Sunday, March 27, 2005

Class 8 - Selection Sets

Hello,

On this class we will cover the first ways we can interact with user to allow our application to get information from drawing screen You probably will need to use this method inside your application.

Introduction

This is one of the most important ways to interact with user because it will allow you to get information from drawing screen through selected entities. Some times you will request user to select entities individually and sometimes you will select them using a filter.

A selection set is a group of entities which are currently selected by an user or by an application. The most important concept involved when selecting entities from screen is that AutoCAD will return their names through a type called ads_name. This type contains the entity name (which is valid only on the current session) and it can be converted to ObjectId using the acdbGetObjectId() global function:

Acad::ErrorStatus acdbGetObjectId (AcDbObjectId& objId, const ads_name objName);

This function receives the ads_name and convert it to an AcDbObjectId. Most of selection set functions will still use the ads_name as parameters and on theses cases you don't need to convert it. The ads_name can store several entities or just one. This will depend on how you or the user has performed the selection.

The selection is made using a function called acedSSGet() which will apply a selection or prompt the user to do that. The function signature is:
int acedSSGet (const ACHAR *str, const void *pt1, const void *pt2,
const struct resbuf *entmask, ads_name ss);
How to use

It receives a selection option, two points, a mask and returns the resulting selection set. After use the selection set it needs to be released and this is done through the acedSSFree() function The selection option will instruct AutoCAD interface to do one of the following methods:

Selection Code

Description

NULL

Single-point selection (if pt1 is specified)
or user selection (if pt1 is also NULL)

#

Nongeometric (all, last, previous)

:$

Prompts supplied

.

User pick

:?

Other callbacks

A

All

B

Box

C

Crossing

CP

Crossing Polygon

:D

Duplicates OK

:E

Everything in aperture

F

Fence

G

Groups

I

Implied

:K

Keyword callbacks

L

Last

M

Multiple

P

Previous

:S

Force single object selection only

W

Window

WP

Window Polygon

X

Extended search (search whole database)


This way we can perform the selection by several ways. Some examples are presented below:
ads_point pt1, pt2;
ads_name ssname;
pt1[X] = pt1[Y] = pt1[Z] = 0.0;
pt2[X] = pt2[Y] = 5.0; pt2[Z] = 0.0;
// Get the current PICKFIRST or ask user for a selection
acedSSGet(NULL, NULL, NULL, NULL, ssname);
// Get the current PICKFIRST set
acedSSGet(_T("I"), NULL, NULL, NULL, ssname);
// Repeat the previous selection set
acedSSGet(_T("P"), NULL, NULL, NULL, ssname);
// Selects the last created entity
acedSSGet(_T("L"), NULL, NULL, NULL, ssname);
// Selects entity passing through point (5,5)
acedSSGet(NULL, pt2, NULL, NULL, ssname);
// Selects entities inside the window from point (0,0) to (5,5)
acedSSGet(_T("W"), pt1, pt2, NULL, ssname);

Using Selection filters

Filters are a powerful way to speed up selection sets and avoid runtime operations to verify entities. You can use single filters or composed filters. Each filter is specified through a structure called resbuf. A resbuf is a linked list which store several types of information and may contains several items. To use a filter we need to construct it and pass it as a parameters of acedSSGet() method. The selection is performed but each selected entity will need to respect the filter. There are a lot of filters we can create and the SDK documentation cover all of them. The most used examples are presented below:
struct resbuf eb1, eb2;
TCHAR sbuf1[10], sbuf2[10];
ads_name ssname1, ssname2;
eb1.restype = 0; // Entity name filter
_tcscpy(sbuf1, _T("CIRCLE"));
eb1.resval.rstring = sbuf1;
eb1.rbnext = NULL;
// Retrieve all circles
acedSSGet(_T("X"), NULL, NULL, &eb1, ssname1);
eb2.restype = 8; // Layer name filter
_tcscpy(sbuf2, _T("0"));
eb2.resval.rstring = sbuf2;
eb2.rbnext = NULL;
// Retrieve all entities on layer 0
acedSSGet(_T("X"), NULL, NULL, &eb2, ssname2);

Modifying entities through a selection set

To modify entities inside a selection set we need to walk through selection items, get each one, convert the ads_name to an ObjectId, open the entity for write, modify it and then close it. This operation can also be done using a transaction which is, in long operations, much better.

To show you how to walk through a selection set I will present a short code to select all CIRCLE entities inside the drawing and then change its color to red. The operation is pretty simple and is done this way:

// Construct the filter
struct resbuf eb1;
TCHAR sbuf1[10];
eb1.restype = 0; // Entity name
_tcscpy(sbuf1, _T("CIRCLE"));
eb1.resval.rstring = sbuf1;
eb1.rbnext = NULL;

// Select All Circles
ads_name ss;
if (acedSSGet(_T("X"), NULL, NULL, &eb1, ss) != RTNORM){
acutRelRb(&eb1);
return;
}

// Free the resbuf
acutRelRb(&eb1);

// Get the length (how many entities were selected)
long length = 0;
if ((acedSSLength( ss, &length ) != RTNORM) (length == 0)) {
acedSSFree( ss );
return;
}

ads_name ent;
AcDbObjectId id = AcDbObjectId::kNull;

// Walk through the selection set and open each entity
for (long i = 0; i < length; i++) {
if (acedSSName(ss,i,ent) != RTNORM) continue;
if (acdbGetObjectId(id,ent) != Acad::eOk) continue;
AcDbEntity* pEnt = NULL;
if (acdbOpenAcDbEntity(pEnt,id,AcDb::kForWrite) != Acad::eOk)
continue;
// Change color
pEnt->setColorIndex(1);
pEnt->close();
}

// Free selection
acedSSFree( ss );


I have used some new functions (like acdbOpenAcDbEntity) that are also part of ObjectARX SDK. Pay attention to the memory releases regarding to selection set and resbuf types. Note that I have used also a function called acedSSLength() to get the length of selection set.
The acedSSName() function get an at the passed index. If we have more than one entity selected this loop will get every single entity into this selection set.

See you next class.

Tuesday, March 22, 2005

AutoCAD 2006 - Interview with Albert Szilvasy (Autodesk)

Hello,

I would like to present a short interview that I made (by e-mail) with a friend from Autodesk. His name is Albert Szilvasy and he is one of the best programmers I ever know from ADN. Since 1999 he is one of the main developers of AutoCAD. See the full interview below. Hope you enjoy!

[Fernando:] Albert, how long are you an ADN / AutoCAD team member?

[ALBERT:] I think I I've been an ADN member since '95. I joined Autodesk in '97 (ADN support) and the AutoCAD team in '99.

[Fernando:] Autodesk has just released the AutoCAD 2006 which has a lot of new features. Which one is your favorite?

[ALBERT:] I'm afraid I'm a bit jaded: my favorite is the expanded .NET API.

[Fernando:] AutoCAD 2006 has increased the .NET API implementation? How long you expect finish to expose the whole API?

[ALBERT:] I don't think we will ever "finish". As we add new features to AutoCAD the .NET API will evolve. The .NET API already covers 95% of what one can do with ObjectARX in C++. I expect that the final gap between the unmanged C++ and .NET API will decrease over time. In fact, I expect that some features will be exposed in the .NET API only and not in ObjectARX since their underlying implementation will be managed code.

[Fernando:] .NET will be just one more customization option or Autodesk plans to remove AutoLISP or VBA soon?

[ALBERT:] We have no plans to remove AutoLISP or VBA. Customers rely on these technologies. We actively encourage customers to use the .NET API for new work simply because we believe it is a more productive environment.

[Fernando:] Is now possible to create custom objects using .NET API? If so, is there any sample in SDK?

[ALBERT:]No, it isn't possible to create custom objects using the .NET API. However, we do have a sample that shows you how to wrap custom objects so that they are accessible from any .NET language. The wrapper is for the "Polysamp" sample. It is on the ObjectARX SDK for AutoCAD 2006.
We have had a lot of internal debate if we should expose the creation of custom objects to .NET developers. We decided that we have higher piority items for AutoCAD 2006. This debate is still ongoing. In my opinion custom objects is an overused feature. Many times developers want to be able to "subclass" existing entities on a instance by instance basis. I think we could provide more value to developers if we allowed them to do this instead of simply re-exposing the existing custom object APIs.
Compare the ObjectARX custom object mechanism to what Windows offers with window classes: you can register new window classes (this is roughly equivalent to our custom objects) but you can also subclass existing windows. You can take an existing window instance and change its behavior completely. AutoCAD Map provides similar capability with its "stylization" feature: it essentially hijacks the worldDraw/viewportDraw function for certain instances. I think this mechanism could be generalized to allow 3rd party developers to specify custom callback for "transformBy", "getGripPoints", "worldDraw" etc. on an instance by instance basis. We are looking for the opinions of the community to figure out where to go next here.

[Fernando:] The only option to load .NET modules is still only through the NETLOAD command? Is there a way to unload them in 2006?

[ALBERT:] To load a .net module you either use the netload command or demand loading registry keys (you need to use the special "MANAGED"
dword value to mark the dll as a .NET module). You still can't unload assemblies in AutoCAD 2006. The CLR does not support unloading of individual assemblies. It only supports unloading entire Appdomains.
This is a complication that we didn't want to introduce at this stage.
We may do it in a future release though since .NET 2.0 makes working with AppDomains easier. Nevertheless, I expect that the new Edit and Continue feature in Visual Studio 2005 will make the ability to unload assemblies a less requested feature.

[Fernando:] The new interface customization without the old MNU based files will provide new features? Is there a new COM interface to customize menus and toolbars?

[ALBERT:] Yes. We switched to a new format so that we can introduce new features easier. The COM API to customize toolbars and menus at runtime hasn't changed and works as it has been since R2000. Customers have asked for more programmetic access to the CUI file itself so we are looking into that.

[Fernando:] The major problem in VC.NET is about application load speed because .NET framework initialization takes some seconds to start. AutoCAD 2006 has improved this?

[ALBERT:] AutoCAD initializes the .NET framework on a background thread during startup. So if you have multiple processor (or multiple cores on your CPU) then the startup impact should be very little. .NET applications that are demand loaded during an AutoCAD session will load very fast since most of the .NET framework (mscorwrks.dll, system.dll,
system.windows.forms.dll) are already loaded by AutoCAD during startup.

[Fernando:] In AU2004 Autodesk has talked about the community involved around its products. How do you see the future of ADN network and AutoCAD developer communities and professionals?

[ALBERT:] I'm a technologiest. I don't really have much opinion here.
The world is becoming more interconnected so I think communities like the Autodesk Developer Network will become more important. I see that trend with Microsoft and other industry heavyweights with large developer communities.

[Fernando:] What can you tell us about the next AutoCAD releases in terms of customization features?

[ALBERT:] I can't really talk about specifics. I recommend that you look at the trends of the last few years and extrapolate. I'd expect that you won't be far off the mark.

Thank you Albert!

Monday, March 21, 2005

Class 7 - Containers

Hello,

On this class I will present the concepts and features of ObjectARX container objects. We have talked a little bit about them before but now we will go into further details.

Introduction

The container object purpose is to store and manage objects of the same type or class. There are two types of containers: Symbol Tables and Dictionaries. Each type of container has some specific functionalities that were designed to allow easy and efficient access methods.

Symbol Tables

This type of container is designed to store the so called records. Each Symbol Table object store its records using an unique entry name. Through this entry you can obtain the record pointer and read or write information. The container may also receive new entries or even has entries removed (in case they are not used by other objects).

To walk through an object container entries you will need to use a proper iterator which will allow you to get entries and access its objects. AutoCAD has some Symbol Tables to store layers, linetypes, text styles and other objects. As these containers work almost the same way, there is a common base class for each of Symbol Tables, its records and the proper iterators.

The Symbol Table class tree is as follows:

AcDbSymbolTable
AcDbAbstractViewTable
AcDbViewportTable
AcDbViewTable
AcDbBlockTable
AcDbDimStyleTable
AcDbLayerTable
AcDbLinetypeTable
AcDbRegAppTable
AcDbTextStyleTable
AcDbUCSTable

AcDbSymbolTableRecord

AcDbAbstractViewTableRecord
AcDbViewportTableRecord
AcDbViewTableRecord
AcDbBlockTableRecord
AcDbDimStyleTable
Record
AcDbLayerTable
Record
AcDbLinetypeTable
Record
AcDbRegAppTable
AcDbTextStyleTable
AcDbUCSTable

AcDbSymbolTableIterator

AcDbAbstractViewTableIterator
AcDbViewportTableIterator
AcDbViewTableIterator
AcDbBlockTableIterator
AcDbDimStyleTable
Iterator
AcDbLayerTable
Iterator
AcDbLinetypeTable
Iterator
AcDbRegAppTable
Iterator
AcDbTextStyleTable
Iterator
AcDbUCSTable
Iterator

So, to create a layer, for instance, you will need to:
  • Open current Database;
  • Open AcDbLayerTable (for write);
  • Create an AcDbLayerTableRecord (using new operator);
  • Configure the AcDbLayerTableRecord;
  • Add it to AcDbLayerTable which is its proper container;
  • Close the record;
  • Close the container.
void createLayer() {
AcDbLayerTable *pLayerTbl = NULL;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pLayerTbl, AcDb::kForWrite);

if (!pLayerTbl->has(_T("MYLAYER"))) {
AcDbLayerTableRecord *pLayerTblRcd = new AcDbLayerTableRecord;
pLayerTblRcd->setName(_T("MYLAYER"));

AcCmColor color;
color.setColorIndex(1); // red
pLayerTblRcd->setColor(color);
pLayerTbl->add(pLayerTblRcd);
pLayerTblRcd->close();

} else acutPrintf("\nLayer already exists");
pLayerTbl->close();
}


To list all existing layers:
  • Open current Database;
  • Open AcDbLayerTable (for read);
  • Create an AcDbLayerTableIterator;
  • Perform a loop through container entries;
  • Get the key name for each entry;
  • Close the container.

void iterateLayers() {
AcDbLayerTable* pLayerTbl = NULL;
acdbHostApplicationServices()->workingDatabase()
->getSymbolTable(pLayerTbl, AcDb::kForRead);

AcDbLayerTableIterator* pLayerIterator;
pLayerTbl->newIterator(pLayerIterator);

AcDbLayerTableRecord* pLayerTblRcd;
TCHAR* pLName;
for (; !pLayerIterator->done(); pLayerIterator->step()) {
pLayerIterator->getRecord(pLayerTblRcd, AcDb::kForRead);
pLayerTblRcd->getName(pLName);
pLayerTblRcd->close();
acutPrintf(_T("\nLayer name: %s"),pLName);
acutDelString(pLName);
}
delete pLayerIterator;
pLayerTbl->close();
}


Dictionaries

This type of container is designed to store generic AcDbObject derived class objects. This container is very useful because we can also store our custom objects inside it. The dictionary structure is much like a tree structure where we have nodes and entries. Inside the same node, entries can not repeat its name because they need to be unique inside the same level. These are the so called Key entries and each Key entry will map to an AcDbObject pointer which can be retrieved directly or through an interator (AcDbDictionaryIterator).

To store an object we need to create an entry using the setAt() method passing also the object pointer which we already have instantiated with the new operator. After add this object we need to close() it. AcDbDictionary container will return the given AcDbObjectId for each entry.

This container is also used by some AutoCAD features like groups and multiline styles. We will cover more about Dictionaries on the Custom Objects chapter.

Sunday, March 20, 2005

Lab 1 - Solved

Hello,

Hope you did solve this lab. I will present my solution now but this doesn't mean that this is the best or the only way to go.

After have created the project and added the two commands as the Lab instructions said, open the acrxEntryPoint.cpp file, move your cursor into CARXLAB1App class and add the following code:

static AcDbObjectId addToModelSpace(AcDbEntity* pEnt) {

AcDbObjectId entId = AcDbObjectId::kNull;
// Get Block Table
AcDbBlockTable* pBlockTable = NULL;
acdbHostApplicationServices()->workingDatabase()
->getBlockTable(pBlockTable,AcDb::kForRead);
// Get Block Table Record (Model Space)
if (pBlockTable) {
AcDbBlockTableRecord* pBTR = NULL;
// Open ModelSpace (for write)
pBlockTable->getAt(ACDB_MODEL_SPACE,
pBTR,AcDb::kForWrite);
if (pBTR) {
// Add entity and get its ObjectId
pBTR->appendAcDbEntity(entId,pEnt);
// Close BTR
pBTR->close();
}
// Close Block Table
pBlockTable->close();
}
return entId;
}

static void ARXLAB1_CENTS(void) {
// First, let's create the Circle
AcGePoint3d cenPt(0,0,0);
AcDbCircle* pCirc = new AcDbCircle(cenPt,
AcGeVector3d::kZAxis,10.0);
// Add the circle to database and close it
addToModelSpace(pCirc);
pCirc->close();

// Now, let's create the Line
AcGePoint3d startPt(0,0,0);
AcGePoint3d endPt(10,10,0);
AcDbLine* pLine = new AcDbLine(startPt,endPt);
// Add the line to database and close it
addToModelSpace(pLine);
pLine->close();
}

static void ARXLAB1_CHENTS(void) {
ads_name en;
ads_point pt;
// Prompt for selection
if (acedEntSel(_T("\nSelect an entity: "),
en, pt) == RTNORM) {
AcDbObjectId eId = AcDbObjectId::kNull;
// Get the ObjectId from ads_name
acdbGetObjectId(eId, en);

AcDbEntity* pEnt = NULL;
// Open entity (for Write) to chance its color
if (acdbOpenObject((AcDbObject*&)pEnt,
eId,AcDb::kForWrite) == Acad::eOk) {
pEnt->setColorIndex(1);
// Close the entity!
pEnt->close();
}
}
else acutPrintf(_T("\nCommand aborted."));
}

The first function, called addtoModelSpace() is a generic utility function which adds an arbitrary entity to ModelSpace and return its ObjectId. Following this function you will find two functions that were linked with CENTS and CHENTS commands.

All 3 funtions are placed into your application class. Pay attention to the code flow and be careful to not forget any braces or close() methods.

You may download this sample from here: ARXLAB1.zip!

Tuesday, March 15, 2005

Lab 1 - Creating and Editing Entities

Hello,

Our first Lab will cover all presented contents since the course beginning. The main idea is to keep this lab simple to consolidate all knowledge present so far.

Requirements:

* Class 1 to 6;
* AutoCAD 2004/2005 or compatible vertical installed;
* Visual Studio .NET 2002 installed;
* ObjectARX Wizard installed.

Objectives:

Create a simple ObjectARX module with 2 commands: CENTS and CHENTS. The first command will create a simple circle (center on 0,0,0 - radius = 10) and a line (from 0,0,0 to 10,10,0). The second command will prompt user to select an existing entity and then will change its color to 1(red).

Instructions:

- Create a project called ARXLAB1 using ObjectARX Wizard;
- Click on the a> icon at ARXWizard's toolbar to open command dialog;
- Right click on the above portion and select New;
- Change the global an local name for CENTS and select Modal as command mode;
- Repeat the process for the CHENTS command;
- Click OK and inspect acrxEntryPoint.cpp file to see the two created functions;
- Observe that two MACRO entries were added at the end of this file;
- To select an entity to change its color, use the following procedure (trust me):

ads_name en;
ads_point pt;
if (acedEntSel(_T(
"\nSelect an entity: "), en, pt) == RTNORM) {
AcDbObjectId eId = AcDbObjectId::kNull;
acdbGetObjectId(eId, en);
}


Tips:

- Don't forget to call close() the entities after open or create them;
- Explore the code to understand what ARX Wizard has created for you;
- Pay attention to code syntax;
- Use the ObjectARX documentation when necessary;

Support:

If you have any questions please post your issue on this article to share with others.

Expected time:

- I will give you 3 days to accomplish this Lab;
- After this period, I will post my suggested solution for this.

Wednesday, March 09, 2005

Class 6 - Entities

Introduction

Entities are objects that has a graphical representation. They could be simple or complex depending on how many features and functionalities they implement. Entities are stored into BlockTableRecord container objects. Each of these containers will keep its entities until they are erased or Database is destroyed. As any other database resident object, each entity can be accessed through its unique ObjectId. Using its ObjectId we can then acquire its pointer (for Read, Write or Notify as we saw on previous class) and then perform desired operations.

Some special entities contains also another objects to simplify implementation and management. A good example of this approach is AcDb3dPolyline which has a collection of AcDb3dPolylineVertex objects that represents each of its vertexes.

Entity Properties


AutoCAD entities has several properties and some of them are common to all kind of entity. These properties are stored into entity's base class called AcDbEntity. This class, also derived from AcDbObject implements several common functionalities that will be used by every derived class and related implemented entity.

If we create a circle (AcDbCircle), for example, it will contain some properties that came from AcDbEntity. These properties are:

  • Color
  • Linetype
  • Linetype scale
  • Visibility
  • Layer
  • Line weight
  • Plot style name

These properties has specific access methods that will allow you to read or modify them accessing the AcDbEntity base class. So, if we get our AcDbCircle entity and would like to change its color we just need to open it for Write, access the proper method and then close the circle.

If we need to build an application that only access these properties we really don't need to know what kind of entity we are opening. In this situation we just need to open our entity, get its pointer as an AcDbEntity pointer and access the desired method.

Entity Transformations

Each AutoCAD entity is placed into a 3D space. You already know that we can move, rotate, scale, align and many other modifications over an entity. AutoCAD threats most of these operations using geometric algebra using matrixes. Remember that we have talked about ObjectARX classes and, specially in this case, about AcGe prefixed classes. The AcGe classes are geometric classes which will allow you to perform simple and complex geometric operations inside AutoCAD.

So let's suppose you need to perform a rotation over several entities (circles, polylines, lines, etc.) and need to do this with minimum effort and basic geometric knowledge. No big deal! We just need to build a transformation matrix and call the appropriate method called transformBy() implemented by AcDbEntity class. Yes, every entity could be potentially transformed!

This function receives an object of class AcGeMatrix3d which represents the matrix to be applied to the entity that will perform some geometric operation. This could be a transformation matrix, a rotation matrix and so on.

This class has wonderful utility functions that will reduce your work a lot! Among these functions I would like to quote the following:

  • setToRotation: You pass in the desired angle (in radians), the axis vector of rotation and the point of rotation. With this parameters this function will fill the AcGeMatrix3d with all elements. After that, just call your entity's transformBy() method passing this matrix in;
  • setToTranlation: You just pass in a 3D vector (AcGeVector3d) which represents the transformation you would like to perform. After that, do the same operation as mentioned above;
  • setToMirroring: This function has 3 versions that receives a point (AcGePoint3d), a line (AcGeLine3d) or a plane (AcGePlane). Depending on what type of parameter you pass in it will build the proper matrix to mirror your entity! Great hum?

Entity Intersection

Another important functionality implemented at AcDbEntity level is entity intersection. Probably one of your future products will need to analyze entities intersections. The method intersectWith() is the responsible to do this job for you.

The most common signature of this method receives the argument entity pointer (the entity you would like to test with yours), the intersection type, an array of 3D points to be filled out by this functions with intersection points found and, optionally the GS marker of both entities which represents the subentity index.

The intersection type must be one of the following operands:

  • kOnBothOperands: neither entity is extended;
  • kExtendThis: extend this entity;
  • kExtendArg: extend argument entity;
  • kExtendBoth: extend both entities.

If these two entities intercept each other (obviously depending on which type of intersection you specify) the passed array will receive the intersection points. This function is very useful and uses the core geometric engine of AutoCAD which make it fast and reliable.

Our next class will be a demo example and I would give you a couple of days to accomplish it and then I will post my solution to it. Stay tuned!

Sunday, March 06, 2005

Class 5 - Object Management

Introduction

On previous class we have talked about object lifecycle. On this class we will go further on how to manage AutoCAD objects. As I have mentioned every object has its own identification called ObjectId. This is the key for acquiring its pointer and perform read or write operations.

The standard access method is made by an OPEN operation (for write, for read or for notify), some operations and a CLOSE method. Another approach, much more efficient is through transactions. This mechanism is much more secure and efficient. Let's talk about both methods.

Using standard OPEN / CLOSE method

This is the most used method but, in other hand, is the most unsafe because you may forget to close the object and then cause access violations or even fatal errors that will cause AutoCAD to terminate.

The global standard function to open objects is called acdbOpenObject(). This function will open every object derived from AcDbObject class and will provide you a C++ pointer to access the object properties and methods. One of this function signatures is the following:

inline Acad::ErrorStatus acdbOpenObject(AcDbObject *& pObj, AcDbObjectId id, AcDb::OpenMode mode, bool openErased);

pObjOutput pointer to the opened object
idInput the object ID of the object to open
modeInput mode to open object
openErasedInput Boolean indicating whether it's OK to open an erased object

This function receives an empty pointer to AcDbObject by reference that will be filled out by AutoCAD if there is an object with the provided input variable id. Further you need to provide your action intention on the object which can be WRITE, READ or NOTIFY. The latest parameter indicate if you would like to open the object if it is on erased status. Remember we have talked that erased objects remain inside AutoCAD database until the next save operation.

The opening intention is very important because it will limit or not what you can do with the object. If you open an object for READ you will not be able to call methods that modify the object's state. In other hand, if you open the object for WRITE you will be able to both modify and read object data. Ah, so is better to always open the object for WRITE?

definitely NOT!

When you open an object for WRITE AutoCAD fires several pre and post procedures on it that causes some performance overhead. If your routine opens several objects and you use the WRITE flag you will certainly loose performance.

The same object can be open for READ up to 256 times without close but it is not recommended. You should always close the object as soon as possible. If an object is opened for WRITE you can't open it a second time for WRITE. Basically, you need to follow the following rules:

Opening objects in different modes
Object opened for:kForReadkForWritekForNotify
openedForReadeAtMaxReaderseWasOpenForRead(Succeeds)
openedForWriteeWasOpenForWriteeWasOpenForWrite(Succeeds)
openedForNotifyeWasOpenForNotifyeWasOpenForNotifyeWasOpenForNotify
wasNotifying(Succeeds)eWasNotifyingeWasNotifying
UndoeWasOpenForUndoeWasOpenForUndo(Succeeds)


The best performance approach is to always open the object for READ, analyze if you will modify it and only after this analysis, upgrade its open operation to WRITE using the upgradeOpen() method. It will switch the object's state from READ to WRITE. To get back to READ use the downgradeOpen() method. These methods are very useful. A simple operation to use these methods would be:

void changeColor(AcDbObjectId id) {
AcDbEntity* pEnt = NULL;
if (acdbOpenObject(pEnt, id, AcDb::kForRead) == Acad::eOk) {
if (pEnt->colorIndex() != 3) {
pEnt->upgradeOpen();
pEnt->setColorIndex(3);
}
else {
acutPrintf(_T("\nEntity already has color=3"));
}
pEnt->close();
}
}


Using Transaction method

Transactions are a better and much more efficient method to manage objects. They can be nested and this allows you to perform long operations without the limitation of READ and WRITE states. Basically you need to open a transaction, perform your desired modifications, and at the end, perform an end or abort transaction operation.

Transactions are pretty good to use when your application uses dialog boxes that change objects. At your dialog opening you start a new transaction and, depending on user click on OK or CANCEL button, you call end or abort transaction methods.

When you abort a transaction the whole contained modification are cancelled. In fact, modifications are really applied only when you end a transaction. Another great feature is that you can open an object for WRITE several times as you can open for READ at the same time.

You don't need to close objects opened through a transaction. The end or abort method will close every object opened and will perform modifications as necessary. It is not recommended to mix standard OPEN / CLOSE approach with TRANSACTIONS due some design limitations. You could read more details about this inside SDK documentation. Now, let's the see the same operation we did above using transactions:

void changeColor(AcDbObjectId id) {
AcDbEntity* pEnt = NULL;
acdbTransactionManager->startTransaction();
if (
acdbTransactionManager->getObject((AcDbObject*&)pEnt, id, AcDb::kForRead) == Acad::eOk) {
if (pEnt->colorIndex() != 3) {
pEnt->upgradeOpen();
pEnt->setColorIndex(3);
}
else {
acutPrintf(_T("\nEntity already has color=3"));
}
}
acdbTransactionManager->endTransaction();
}

This time you open the object using the getObject() method which is much like acdbOpenObject() but YOU DON'T NEED TO CLOSE the object. The whole process is ended at endTransaction() method call. At that time all operations are applied in one operation.

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"));
}