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!

108 comments :

Fernando Malard said...

Hi Carlos,

You may use something like this:

static void ListAttributes_LISTATT(void) {
ads_name ename ;
ads_point pt ;

if (acedEntSel ("Please, select an existing block: ", ename, pt) != RTNORM)
return;

AcDbObjectId id = AcDbObjectId::kNull;
acdbGetObjectId (id, ename) ;

if (id == AcDbObjectId::kNull)
return;

AcDbBlockReference *pRef = NULL;
// Open the entity (it will be closed at the end of this funcion
if (acdbOpenAcDbObject ((AcDbObject *&)pRef, id, AcDb::kForRead) != Acad::eOk) {
acutPrintf("\nError opening the entity!!!");
return;
}

// Check if it is a BlockReference
if (!pRef->isKindOf(AcDbBlockReference::desc())) {
pRef->close();
acutPrintf("\nPlease, select a block reference!!!");
return;
}

// First we will look into BlockReference entity looking for
// all non-constant attributes which are stored into block insertions
AcDbObjectIterator *pIter = pRef->attributeIterator() ;
acutPrintf("\nBlock's Non-constant Attributes:");
while ( !pIter->done () ) {
AcDbEntity *pAttEnt = NULL;
AcDbObjectId idAtt = pIter->objectId();
if (acdbOpenObject(pAttEnt,idAtt,AcDb::kForRead) == Acad::eOk) {
if (pAttEnt->isKindOf(AcDbAttribute::desc())) {
// Here you can read Attribute information
AcDbAttribute* pAtt = AcDbAttribute::cast(pAttEnt);
CString msg;
msg.Format("\nTAG:%s - VALUE:%s",pAtt->tag(),pAtt->textString());
acutPrintf(msg);
}
pAttEnt->close();
}
pIter->step();
}
delete pIter ;

// Now we will open the main block definition (BlockTableRecord)
// and will look for the Constant Attribute information
id = pRef->blockTableRecord();
AcDbBlockTableRecord *pRec = NULL;

if (acdbOpenAcDbObject((AcDbObject*&)pRec,id,AcDb::kForRead) == Acad::eOk) {
if ( pRec->hasAttributeDefinitions () == Adesk::kTrue ) {
// Create a new iterator
AcDbBlockTableRecordIterator *pIter2 = NULL;
pRec->newIterator(pIter2) ;
acutPrintf("\n\nBlock's Constant Attributes:");

// Walk through attributes
while ( !pIter2->done () ) {
AcDbEntity *pEnt = NULL;
pIter2->getEntity(pEnt, AcDb::kForRead) ;

if (pEnt) {
if ( pEnt->isKindOf (AcDbAttributeDefinition::desc ()) ) {
AcDbAttributeDefinition *pDef = AcDbAttributeDefinition::cast(pEnt);
if ( pDef->isConstant () == Adesk::kTrue ) {
CString msg;
msg.Format("\nTAG:%s - VALUE:%s",pDef->tag(),pDef->textString());
acutPrintf(msg);
}
}
pEnt->close();
}
// Go to the next
pIter2->step();
}
delete pIter2 ;
}
// Close the BTR
pRec->close () ;
}
// Close the entity
pRef->close () ;
}

Fernando Malard said...

Carlos,

If you prefer, download the sample from here:

http://www.drawex.com/ThirdParty/files/ObjectARX/Samples/ListAttributes.zip

Regards,
Fernando.

Anonymous said...

Carlos:

Nice article.

Can you give me a sample of IntersectsWith code in .Net? I am basically looking to find all blocks which touch an endpoint or midpoint of my block and need to report on that.

Naresh Nichani

Anonymous said...

Dear Fernanodo

I have some problem
1- I have MFC Dialog that contain 2 button on click first button the all entity colored with red and on click second button each entity reset to original color (black ... red ...Green…)

What is the best way to do Color and undo Color?


2- I have MFC Dialog that contain 2 buttons on click first the all entity hide and on click second click second button each entity reset hide

What is the best way to do this?

Best Regards
M.S

Anonymous said...

Dear Fernanodo

I have a problem
1- I have MFC Dialog that contain 2 button on click first button the all entity colored with red and on click second button each entity reset to original color (black ... red ...Green…)

What is the best way to do Color and undo Color?
Is any way to attach data to entity?

Anonymous said...

Dear All

please how to delete entity if I get Object id

Regards,
Ahmad Ali

Fernando Malard said...

Hi M.S.,

The only way to "remember" the old value is storing it inside somewhere. You may use the XData or XRecord of each entity and store the last value of color. With this information your command will be able to retrieve this information even at another drawing session.

Take a look at the sample:
\ObjectARX 2007\samples\database\xrecord_dg

Regards,
Fernando.

Fernando Malard said...

Hi Ahmad,

You can not delete a database resident entity. You need to open it for write, call its erase() method and then call close() method.

This will remove the entity from its database owner but will not free its used memory (this is a known AutoCAD API limitation).

Regards,
Fernando.

Anonymous said...

Hi Fernando,

How to export some entity from drawing file to another drawing.

cheers
Jawad

Fernando Malard said...

Hi Jawad,

There is a sample which demonstrate this. The process is called wblockclone.

Take a look at:
ObjectARX2007\Samples\Database\Clones

The process can be complex depending on what your entity may contain or refer to.

Further, take a look at the AcDbDatabase::wblockCloneObjects() method. A good example of its use is inside ArxDbgCmdTests::testWblockCloneObjects() method at the sample:
ObjectARX2007\Samples\Database\ArxDBG

Anonymous said...

Hi

How to copy entityt to another entity

I need to put value of entity pEntity1 to pEntity2

AcDbEntity * pEntity1;//line Entity
AcDbEntity * pEntity2;//Empty

Jawad.

Anonymous said...

I use Clone is it valid

and how to convert AcRxObject to AcDbEntity


Jawad.

Anonymous said...

I cant understand parameter 4 from this line

es = fromDb->wblockCloneObjects(cloneSet, destBlkId, idMap, static_cast< AcDb::DuplicateRecordCloning >(prDrc.value()));

Jawad.

Anonymous said...

Dear fernando,

I use ARX sample and it work

I copy entity from dwg to another

Thanks....

but is any way to move entity to exact(is it applicable) layer "layer5" for Example.

Regards,
Jawad.

Fernando Malard said...

Hi,

Take a look at the clone() method. Another approach is to use the AcDbDatabase method called deepCloneObjects().

Regards,
Fernando.

Fernando Malard said...

Hi,

AcRxObject is the base class of almost all ObjectARX classes.

To convert, you just need to use the cast() method which will cast the pointer if possible or set your pointer to NULL if it's not possible to cast.

Regards,
Fernando.

Fernando Malard said...

Hi,

The 4th parameter is about how the process will handle duplicate records. Imagine you have a layer with the same name in both source and destination databases.

In this scenario you have 2 options:

1)Copy your entity and use the existing layer inside the destination database;

2)Copy your entity and its layer replacing the destination database layer. In this case, if this layer has different properties it will affect the other entities inside the destination database which are using this layer.

Regards,
Fernando.

Anonymous said...

Hi Fernando,

i Have this code
void ExportData::SetExportedDataBase(AcDbDatabase *& pExpDB, AcDbDatabase *pCurrentDB, HandleArray handleArray)
{
AcDbObjectId destBlkId;
AcDbObjectIdArray cloneSet;

cloneSet = GetObjectIDByHandleArray(handleArray, pCurrentDB);

AcDbIdMapping idMap;
Acad::ErrorStatus eee = idMap.setDestDb(pExpDB);
destBlkId = pExpDB->currentSpaceId();
Acad::ErrorStatus error= pCurrentDB->wblockCloneObjects(cloneSet,destBlkId, idMap, AcDb::kDrcIgnore);
}

Please how to export cloneset to layer "MyLayer" on new DWG

Note: -The new layer Does not exist in new DWG

Note :Destination DWG is Empty DWG

Regards,
Jawad.

Fernando Malard said...

Hi Jawad,

The layer and all other objects that the cloned objects depend on are automatically cloned too.

If your layer is not used by any object included into your clone set it will not be cloned.

To force its clone you may add this layer's ObjectId into your clone set array.

See if this works and let me know.
Regards,

Fernando.

Anonymous said...

Hi fernando,

sorry My English language is bad.....My mather language is arabic language(I am From Lebanon)

but My scenario is copy all line from orginal DWG(Has Different Layer) to New DWG(Empty DWG) and create on new DWG layer "MyLayer" and then add all line to this layer

NEW DWG is Empty..

Regards,
Jawad

Fernando Malard said...

Hi Jawad,

You will need to first copy all lines and with their new ObjectId you will need to open each one and set the ObjectId of your new layer inside destination database.

This way you will need to first create this new layer, get its ObjectId, clone all lines, get their new ObjectId into destination database. Then you will need to open each new line and set the layerId() with your new layer's ObjectdId.

Regards,
Fernando.

Anonymous said...

Hi Fernanod,

Thanks, I Start with this soloution

Regards,
Jawad

Anonymous said...

Thanks so much Fernando,

I finshed, really it solved my problem.

Regards,
Jawad.

Ioan said...

Hi Fernando!
First of all thank you for all precious responses to my posts!
I know time is critic, hope not to abuse..


I try to create a some parametric 2D shapes.
I just want to use the “classic” blocks, so I do not want to use custom entities, and not even the new dynamic blocks, just simple blocks.
It is possible to have lets say 10 blocks, with 10 different aspects (different sizes, or more/less number of entities!!! in the same block), BUT ONLY ONE, a single definition of the block (AcDbBlockTableRecord)?

If it's possible the block atributes can help here? If block atributes do not help, can I use other methods like in ObjectArx Labs Step 4 - Xrecords - Named Objects Dictionary or Step 5 - Custom Objects - Extension Dictionary?

Best regards!
Ioan

Fernando Malard said...

Ioan,

AcDbBlockTableRecord is an entity container like ModelSpace. It has a unique name definition by instance because its name is a key inside AcDbBlockTable container structure (exactly due that you cannot create two blocks with the same name).

I cannot imagine any other way to do that except by creating a custom entity or a dynamic block.

XRecords will not help you in this case.

Regards.

Ioan said...

Hello Fernando!
I'm working with IAcadBlock COM entities.
How could I determine and acces if exist, of course, the block references for a COM block?

I'm asking if for COM does exist an ObjectARX equivalent like:

...
AcDbBlockTableRecord *pBlockTableRecord ; AcDbObjectIdArray ids;
pBlockTableRecord->getBlockReferenceIds(ids, true, false);
...

It is possible to determine if
this kind of entity BUT ONLY IN COM, has some reference.
Using COM I can get the block, the name of it, but I want to check if has some references (inserts) in the current drawing, and if has to delete these references.

Kind regards!
Ioan

Fernando Malard said...

Ioan,

IAcadBlock does not have this method exposed. You will need to check the entire database and collect every IAcadBlockReference entity. For each one, you will need to use get_Name() and then search for this name inside BlockTable.

I recommend you to create a MAP structure to cache found blocks because next time you don`t need to traverse the BlockTable again searching for the name you already found previously.

Regards.

Ioan said...

Excelent!
Thank you Fernando!
"I recommend you to create a MAP structure.." how it possbile to to this? Can you indicate some few steps, or to point some link to help me? I have never use MAP ..

Best regards!
Ioan

Fernando Malard said...

Ioan, nevermind.
I thought you will need to keep the ObjectId of each BlockTableRecord but you only need to know the BlockReferences names.

So basically you need to get the name of each existing BlockReference and test if it is equal to the desired block name.

Good luck.

Ioan said...

Hello Fernando!
I would like to get some information from a block insert using GetXData.

To write XData is OK using the following code:

long pos0 = 0;
long pos1 = 1;
long pos2 = 2;
long pos3 = 3;
short eString = 100;
short eInteger = 90;

_variant_t vType2Array;
vType2Array.vt = VT_I2|VT_ARRAY;
vType2Array.parray = SafeArrayCreateVector(VT_I2,0,4);
SafeArrayPutElement(vType2Array.parray, &pos0, &eString);
SafeArrayPutElement(vType2Array.parray, &pos1, &eString);
SafeArrayPutElement(vType2Array.parray, &pos2, &eInteger);
SafeArrayPutElement(vType2Array.parray, &pos3, &eInteger);

_variant_t vData2Array;
vData2Array.vt = VT_VARIANT|VT_ARRAY;
vData2Array.parray = SafeArrayCreateVector(VT_VARIANT,0,4);
SafeArrayPutElement(vData2Array.parray, &pos0, &_variant_t("TESTVALUE1"));
SafeArrayPutElement(vData2Array.parray, &pos1, &_variant_t("TESTVALUE2"));
SafeArrayPutElement(vData2Array.parray, &pos2, &_variant_t(long(111111)));
SafeArrayPutElement(vData2Array.parray, &pos3, &_variant_t(long(222222)));

hr = xrecord->SetXRecordData(vType2Array,vData2Array);

I can not obtain XRecod using:


VARIANT vType2Array;
vType2Array.vt = VT_I2|VT_ARRAY;
vType2Array.parray = SafeArrayCreateVector(VT_I2,0,4);
VARIANT vData2Array;
vData2Array.parray = SafeArrayCreateVector(VT_VARIANT,0,4);
vData2Array.vt = VT_VARIANT|VT_ARRAY;
hr =iblock->GetXData(_bstr_t("MYAPP"),&vType2Array,&vData2Array);

CComBSTR szTmp;
int i=3;
SafeArrayGetElement(vData2Array.parray, (LONG*)&i, reinterpret_cast(void*)(&szTmp));


What is wrong?
Kind regards!
Ioan

Fernando Malard said...

Hello Ioan,

I think the problem is the missing Application name when you are storing the data.

The XData structure requires an "application name" as the root of its XData chain.

So adding an application name as the first element of your XRecord XData should solve the problem.

Regards.

Ioan said...

Hello Fernando!
To store the record I have used the following line:

dictionary->AddXRecord(_bstr_t("MYAPP"),&xrecord);
and the application name is the same in the both moments (write/read).

Anyway thank you for all help provided in this virtual space!

Best regards!
Ioan

Fernando Malard said...

Ioan,

XData and XRecord are two different things. Are you mixing these features?

You cannot add the data as XRecord and then get it as XData. You need to use the same data package to set and get.

Check your method names:
SetXRecordData()
and
GetXData()

Regards.

Ioan said...

Hello Fernando!
You have absolute right! I have mixed the SetXRecordData with GetXData..

All I want to do is to retrieve those data set using Xrecord SetXRecordData .

From block insert I can get the dictionary.
The problem is that the dictionary hasn't something like GetXRecordData..

Could you indicate what is the way to get back these data already stored?

Using C++ COM is an absolute dark in the ObjectARX documentation..

Kind regards!
Ioan

Fernando Malard said...

Ioan,

To get XRecord back you will need to open entity XRecords and look for an entry with your name "MYAPP". One you have found it, you can read the information.

The best source of AutoCAD COM documentation is the VBA documentation because VBA uses COM behind the scenes to access AutoCAD. Of course COM syntax is much more complex but the methods are the same.

Regards.

Ioan said...

Thank you Fernando!
Extremely valuable info here!

Best regards!
Ioan

Anonymous said...

Hi Carlos,

thanks for your help, all your suggestions are very useful.

I have a problem with .net.
I try to find an entity (like a text) in the drawing,
and replace the text with another. But any attempt is failed. Can you help me?

Thanks for any idea.

Umbe

Fernando Malard said...

Hello Umbe,

Could you provide the code (or part of it) you are using for this?

Regards.

Anonymous said...

Hi,

How can I delete the XData associated with a DBObject using the managed API? I tried by setting it to nullptr (dbObj->Xdata = nullptr) but it somehow doesn't work.

Fernando Malard said...

Hi,

You need to remove XData through the setXData() method:

"To remove an appName (and its xdata) from an object, just use a resbuf with restype == 1001, resval == and no data resbufs following it (that is, either its rbnext == NULL or the next resbuf is another 1001)"

Remember that xData is per application so you should remove only your application data.

Regards,

Anonymous said...

Thanks, Fernando!

Esaias Pech said...

Hey Fernando,

Sorry to bother you again, I'm able to insert a lot of points into a Layer '0'. Now I'd like to go thru all of those points and add text to each of those points (their 'z').
How can I retrieve those points from Layer '0'?
Also, how can I create a new layer and select it so that the points I add go to that layer??
Another question, I tried to make a Dialog in MFC (customized dialog) but when I call the function in ARX, it says something like "operation not permitted" or something like that, can I make customized dialogs (classes derived from CDialog) and call them?? Or I should just call standard dialogs?

Thank you for your time and answer..

Fernando Malard said...

Hello,

I think the MFC part of your question was solved by the MFC class.

There are several ways of retrieving the entities at some specific layers. Take a look at my selecion set class into the filter subject.

To change the layer you need to get its ObjectId and set it to your class by using the setLayer() method.

To create a new layer you will need to create a new AcDbLayerTableRecord inside AcDbLayer table. This process is well documented into ObjectARX SDK.

Regards.

Anonymous said...

Happy new year Fernando !

I have a question. I'm trying to implement this create line function but i get an error:

left of '->workingDatabase' must point to class/struct/union/generic type
'acdbHostApplicationServices': identifier not found

i have added libs acad.lib rxapi.lib and acdb17.lib (arx 2008)

what am i doing wrong ?

Fernando Malard said...

Hello,

You need to check both your .H (header) and .LIB (library) include paths.

Have you tried to create a new application using ARXWizard and place your code there?

Have you check your VS2005 directories and the ObjectARX location?

Regards,

Racso said...

Hi Fernando:
I can get the sphere center using the following code:
//********************************************
AcDb3dSolid* pSphere=NULL;
acdbTransactionManager->getObject((AcDbObject*&)pSphere, SphereID, AcDb::kForRead);
AcGePoint3d Center;
double volume;
double momInertia[3];
double prodInertia[3];
double prinMoments[3];
AcGeVector3d prinAxes[3];
double radiiGyration[3];
AcDbExtents extents;
pPto->getMassProp(volume,Center,momInertia,
prodInertia,prinMoments,prinAxes,radiiGyration,extents);
acdbTransactionManager->endTransaction();
//********************************************
It works perfect but this function has too many variables. Is there any way I can use the function center from AcGeSphere knowing the sphere ID??

Racso Apres

Fernando Malard said...

Racso,

Have you tried to use getGeomExtents(). It will return the bounding box of your solid. I'm not sure about how precise this method is but you may give a try.

Regards.

Racso said...

Fernando:
As allways, your help works. I used getGeomExtents() to get max and min point, then the center of the sphere is (max+min)/2
Thanks a lot.

Racso.

Anonymous said...

In your example, you have the line:

msg.Format("\nTAG:%s - VALUE:%s",pAtt->tag(),pAtt->textString());

According to the documentation, that is a memory leak! Both tag and textString create duplicate strings that the application is responsible for releasing.

You should use:

msg.Format("\nTAG:%s - VALUE:%s",pAtt->tagConst(),pAtt->textStringConst());

instead. Note that some other functions, like: AcDbEntity::layer() also create copies of strings that need to be released, but has no const-counterpart!

Fernando Malard said...

Yes, you are correct.
Thank you.

Unknown said...

Hi Fernando, I'm using VS2008 Sp1 and Autocad 2010, so I have the ObjectArx 2010 installed. I need to use the parametric surface class, in order to calculate some derivatives (parcial respect to parameter u,v and mixed), but I'm having problems declaring for example a Torus, Sphere, Cone.
ex:
AcGeTorus t1(major_r,
minor_r,Pt_org,Sym_Vector);
I have added this code to the exercise that you explained in Lab1, in the cents void. I'm receiving an error with the linker that says that I have an unresolved external symbol. How can i fix this problem??
Thanks for your help.

Fernando Malard said...

Hi Camilo,

Are you sure your project is linking with acge17.lib or acge18.lib?

Those are supposed libraries where AcGeTorus class is defined.

If you are explicit adding the necessary libraries to your project you will need to add it too.

Let me know if it did work.

Regards.

Unknown said...

Hi Fernando, thanks for your fast answer, I have added the inc, inc-x64 and lib-x64 folder to their respective places in the c++ directories. Do I have to add some other parameters in the linker?
pd: I'm able to use for example the AcGeVector3d, AcGeLine3d classes and even the AcGePlane class without getting any errors.

Fernando Malard said...

Camilo,

This class is not inside the basic libraries. I have found it inside acgex18.lib module which is inside BRep library folder:

\ObjectARX 2010\utils\brep\lib-x64

and

\ObjectARX 2010\utils\brep\lib-win32

If you add this path to your project it should link ok.

Regards.

Unknown said...

Thank you so much, I just compiled without any errors.Just to set this clear so others can solve this issue too: I added the path suggested by Fernando \ObjectARX 2010\utils\brep\lib-x64 (in my case) to the VC++ directories (in the lib files)and then went to project properties>linker>Input>Additional dependencies and wrote there acgex18.lib.
After that I was able to compile without any errors.

Again thanks a lot Fernando!!!

Rajendra said...

Hi Fernando,
I would like to clear Result Buffer of myApplicationName. For this I have tried with
>acutRelRb(pRB);
But When I closed Entity and get Result buffer Its not NULL.
How can I delete completely and assign new resbuf??

Fernando Malard said...

Rajendra,

acutRelRb() does not delete the resbuf but just release the current pointer to the data.

To actually remove, try the following:

// Suppose you have a pEnt pointing to your entity and it was opened for WRITE

void CleanAppData(AcDnEntity* pEnt)
{

struct resbuf *pRb = pEnt->xData( "YourAppName" );

if (pRb == NULL)
{
// The entity does not have my APP data
pEnt->close();
return;
}

pRb = acutBuildList( 1001, "YourAppName", RTNONE );

pEnt->setXData(pRb);
acutRelRb(pRb);

pEnt->close();

}

Please let me know if it works.
Regards,

Fernando Malard said...

*The correct class name is AcDbEntity

Rajendra said...

Hi Fernando,
I am stuck with one problem while confirming the delete.

For gaining the same error i do as follow:
1. Load the arx file.
2. Make line from Autocad function.
3. Make result buffer and append in it.
4. SetResultBuffer.
5. No error in setting.
6. When I try to access result buffer, it gives Null on same application name.

But same process is valid if i make some entity from code and then make line from autocad. this time i can set and get the values of result buffer.

If i make line first(from autocad) and the make entities from program and try to access result buffer. i get null value in result buffer.

What might be the possible reason?

Fernando Malard said...

Rajendra,

This does not make sense.
The fact of creating a line prior to create your own should not affect anything related to XData.

There is a sample called ArxDbg under "\ObjectARX 2010\samples\database\ARXDBG" which contains some XData testing functions. Try to compile this sample and load it inside AutoCAD.

It will create a new right-click entry that you can use for several debugging purposes including XData analysis.

Try to monitor each step of your data manipulation and make sure it is correctly structured.

Try to comment your code and start to test it incrementally by restoring each part and testing the data portion accordingly.

Good luck.

Anonymous said...

How to modify the xdata for an entity.

Fernando Malard said...

Hi,

You will need to retrieve the xData as usual, work with the resbuf.

If you want to completely replace it just set a new resbuf pointing to the same application name.

Take a look at the ArxDbg sample inside ObjectARX SDK. It does demonstrate how to better manipulate xData through an utility class called ArxDbgXdata.

Regards,
Fernando.

Anonymous said...

Hi Fernando!
How to explode an non uniform scaled AcDbBlockReference? explodeToOwnerSpace requires uniform scaled blocks.

Best regards
badziewiak

Fernando Malard said...

Hi badziewiak,

I think you will need to use the basic explode() method which will return an AcDbVoidPtrArray of entities representing your block. You will need to walk through this array and add those entities to the BTR you want them to be inserted.

Regards.

Anonymous said...

Hi Fernando,
I can't download "Course support" and "Course and User samples". Please help me? Thanksss!

Fernando Malard said...

Sorry about the inconvenience. The files were moved to Google docs:

https://docs.google.com/leaf?id=0By7BVn8vCBxnYjZjNWJhZjYtZjE3MC00ZjdiLTlmMjMtNTVhNTNjNjE0YjUy&hl=en

Regards.

Anonymous said...

Hi Fernando.

I wonder if I can access an entity by a cordinate X, Y and get your ads_name but not to click on the segment.

Thank you.

Fernando Malard said...

Hello,

There are some resources to select entities by a crossing/window selection or even by a Fence line.

If you want to select specifically an entity at the XYZ coordinate you would need to select it through a tiny window/crossing around that point.

I would need more information about what you are trying to achieve in order to give more suggestions.

Regards,

Anonymous said...

Hi fernando,

I am drawing a solid in my autocad, I want my command to find that solid automatically, I need to manipulate the solid. can you plz tell me how can I get a pointer to my solid?

Thanks.

Fernando Malard said...

Hi,

You just need to walk through ModelSpace BlockTableRecord and test the class of each entity you find:

=====================================

Acad::ErrorStatus es;
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
AcDbBlockTableRecordPointer pBTR (acdbSymUtil()->blockModelSpaceId(pDb),AcDb::kForRead);

AcDbBlockTableRecordIterator *pIter = NULL;
pBTR->newIterator(pIter, true);
AcDbObjectIdArray arrLines;

while(!pIter->done())
{
AcDbEntity *pEnt = NULL;
// Open the entity for READ because we don't know whether it is a solid or not
es = pIter->getEntity(pEnt, AcDb::kForRead);
if (es == Acad::eOk)
{
if (pEnt->isKindOf(AcDb3dSolid::desc()))
{
AcDb3dSolid* pSolid = AcDb3dSolid::cast(pEnt);
if (pSolid)
{
// Do your stuff here...
// If you plan to change the solid, you need to turn the pointer into a "write-enabled" once it is opened for READ
}
}

pEnt->close();
}

pIter->step(true);
}
delete pIter;
pIter = NULL;

=====================================

Hope this help.

Anonymous said...

Sorry to bother you again. I am drawing one solid in my dwg file, next I am picking another solid from another dwg file and doing subtraction between them. my code breaks after I complete my subtraction using booleanOper API. Could you plz tell me where I am going wrong?

Acad::ErrorStatus es;
AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
AcDbBlockTableRecordPointer pBTR (acdbSymUtil()->blockModelSpaceId(pDb),AcDb::kForRead);

AcDbBlockTableRecordIterator *pIter = NULL;
pBTR->newIterator(pIter, true);
AcDbObjectIdArray arrLines;

while(!pIter->done())
{
AcDbEntity *pEnt1 = NULL;
// Open the entity for READ because we don't know whether it is a solid or not
es = pIter->getEntity(pEnt1, AcDb::kForRead);

if (es == Acad::eOk)
{
if (pEnt1->isKindOf(AcDb3dSolid::desc()))
{
AcDb3dSolid* pSolid1 = AcDb3dSolid::cast(pEnt1);
if (pSolid1)
{
// Do your stuff here...
}
pEnt1->close();



pIter->step(true);

delete pIter;
pIter = NULL;


// Set constructor parameter to kFalse so that the

// database will be constructed empty. This way only

// what is read in will be in the database.

//

AcDbDatabase *pDb = new AcDbDatabase(Adesk::kFalse);



// The AcDbDatabase::readDwgFile() function

// automatically appends a DWG extension if it is not

// specified in the filename parameter.

//

if(Acad::eOk != pDb->readDwgFile(_T("C:/Users/mypc/Desktop/abc.dwg")))

return;

// Open the model space block table record.

//

AcDbBlockTable *pBlkTbl;

pDb->getSymbolTable(pBlkTbl, AcDb::kForRead);


AcDbBlockTableRecord *pBlkTblRcd;

pBlkTbl->getAt(ACDB_MODEL_SPACE, pBlkTblRcd,

AcDb::kForRead);

pBlkTbl->close();



AcDbBlockTableRecordIterator *pBlkTblRcdItr;

pBlkTblRcd->newIterator(pBlkTblRcdItr);


AcDbEntity *pEnt2;

for (pBlkTblRcdItr->start(); !pBlkTblRcdItr->done();

pBlkTblRcdItr->step())

{

pBlkTblRcdItr->getEntity(pEnt2,

AcDb::kForRead);
if (pEnt2->isKindOf(AcDb3dSolid::desc()))
{
AcDb3dSolid* pSolid2 = AcDb3dSolid::cast(pEnt2);
if (pSolid2)
{
pSolid2->booleanOper(AcDb::kBoolSubtract,pSolid1);
/*pSolid1->erase();
pSolid2->draw();*/

}
else
{
delete pSolid2;
}

pEnt2->close();

pBlkTblRcd->close();

delete pBlkTblRcdItr;

delete pDb;

AcDbBlockTable *pBlockTable;
acdbHostApplicationServices()->workingDatabase() // code breaks here
->getBlockTable(pBlockTable, AcDb::kForRead);
AcDbBlockTableRecord *pBlockTableRecord;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord,
AcDb::kForWrite);
pBlockTable->close();
AcDbObjectId SolidId2;
pBlockTableRecord->appendAcDbEntity(SolidId2, pSolid2);
pBlockTableRecord->close();
pSolid1->close();
pSolid2->close();

}
}
}
}
}
}

Fernando Malard said...

Hi,

The problem is the boolean operation being processed between solids stored into different databases. AutoCAD does not support this type of process because the entire database is structured with object ownership relationships.
I think you will need to first wblockclone() the external block into the current drawing and then perform the boolean operation.

Probably the call to pSolid2->booleanOper(AcDb::kBoolSubtract,pSolid1); is returning an Acad::ErrorStatus like "eWrongDatabase" or something like that. By the way, you should always check the error returned by these complex functions because they can fail eventually.

Regards.

Anonymous said...

Hi,

Thanks a lot for your precious time. Can I do the subtraction by using longtransaction or do I need to specifically use wblockclone for it?

Regards,

Fernando Malard said...

I don't think LongTransactions are going to work in this case because the boolean operation is processed with geometry placed into the same 3D space. You can give it a try though, but it seems the wblockclone is the only way to do it.

Regards,

Anonymous said...

Hi,

I initially tried with LongTransactions but but boolean operation doesnot work. Now I am using AcDbDatabase::wblockCloneObjects method for cloning. When I use the API, my program creates 2 new layouts, namely layout 3 & layout 4 but the entity is not copied.

My modelspace in the workingdatabase still remains vacant. I want to copy my entity in current working modelspace so that I can perform subtract operation. Can you please correct me out.

AcDbDatabase *pDb = new AcDbDatabase(Adesk::kFalse);

pDb->readDwgFile(L"C:\\Users\\Mypc\\Desktop\\abc.dwg");

AcDbBlockTable *pBlockTable;
pDb->getSymbolTable(pBlockTable, AcDb::kForRead);

AcDbObjectId ObjId;
AcDbObjectIdArray objIdArray;

AcDbBlockTableIterator *pBTableIterator;
if (pBlockTable->newIterator(pBTableIterator) == Acad::eOk)
{
while (!pBTableIterator->done())
{
AcDbBlockTableRecord *pRecord;
if (pBTableIterator->getRecord(pRecord, AcDb::kForRead) == Acad::eOk)
{

pBTableIterator->getRecordId(ObjId);
objIdArray.append(ObjId);

pRecord->close();
}
pBTableIterator->step();
}
}
pBlockTable->close();

AcDbDatabase *pDb1 = acdbHostApplicationServices()->workingDatabase();

AcDbObjectId objId1;
objId1 = pDb1->currentSpaceId();

AcDbIdMapping idMap;
idMap.setDestDb(pDb1);

Acad::ErrorStatus es = pDb1->wblockCloneObjects(objIdArray, objId1, idMap, AcDb::kDrcReplace);
if(es != Acad::eOk)
{
acutPrintf(L"Error: %d\n", es);
}

delete pDb;

Regards

Fernando Malard said...

Hi,

The problem looks like to be at line "objId1 = pDb1->currentSpaceId();". The currentSpaceId() may not be necessarily the ModelSpace. If the drawing has a Layout tab active this method is going to return its ObjectId. You need to explicitly get the ModelSpace objectId.

Regards,

Anonymous said...

Sorry but I didnt find a way to get the ModelSpace ObjectId. Could you plz help me out.

Many Thanks

Fernando Malard said...

AcDbDatabase *pDb = acdbHostApplicationServices()->workingDatabase();
AcDbObjectId modelId = acdbSymUtil()->blockModelSpaceId(pDb);

Anonymous said...

Thanks for the help but the problem persists. The entity is not getting copied anywhere and two new layouts are getting created.

Fernando Malard said...

You are cloning all blocktable records in your routine.
If you want to just clone the ModelSpace entities you will need to change the first portion of your code.

As a reference, take a look at this sample code showing how to clone a block from one DWG to another:

http://adndevblog.typepad.com/autocad/2012/05/insert-block-from-a-different-dwg-using-net-.html

Regards,

Anonymous said...

I wanted to ask why the entity I am copying is not getting displayed in the current database. I have done debugging, my program is able to read the entity but don't know why not displaying it. I am really sorry but I am very new so I am having so many doubts.

Fernando Malard said...

It is difficult to figure this out only by reading your messages.
You will need to check the source DWG to see if the entity placed there is indeed into ModelSpace, its coordinates, Layer, etc.
Check if everything is being copied.

By the way, if your source entity is a LINE, check the end points at the source DWG and where they are at the current drawing.

Try to do it with an empty DWG containing only the entity you want to copy.
Debug your code...you will find it.

Anonymous said...

Ya I have found. My program is copying the entities from the paperspace only. Entities drawn in modelspace are not getting copied.

Regards

Fernando Malard said...

Glad you have found it!

Anonymous said...

I have tried reimplementing my method. I am getting an error eNotHandled when i am closing my blocktablerecord and program is crashing. Can you tell plz why?

Acad::ErrorStatus es;

AcDbCircle *pCirc1 = new AcDbCircle(AcGePoint3d(1,1,1), AcGeVector3d(0,0,1), 1.0),
*pCirc2 = new AcDbCircle(AcGePoint3d(4,4,4), AcGeVector3d(0,0,1), 2.0);

AcDbBlockTable *pBlockTable;
es = acdbHostApplicationServices()->workingDatabase()->getSymbolTable(pBlockTable, AcDb::kForRead);
AcDbBlockTableRecord *pBlockTableRecord;

pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);
pBlockTable->close();

AcDbObjectIdArray ObjIdArr;
ObjIdArr.append(pCirc1->objectId());
ObjIdArr.append(pCirc2->objectId());

AcDbObjectId CircleId;
es = pBlockTableRecord->appendAcDbEntity(CircleId, pCirc1);
es = pCirc1->close();

es = pBlockTableRecord->appendAcDbEntity(CircleId, pCirc2);
es = pCirc2->close();
es = pBlockTableRecord->close();

AcDbDatabase *pDb = new AcDbDatabase();
AcDbBlockTable *pBt;
pDb->getSymbolTable(pBt, AcDb::kForRead);

AcDbBlockTableRecord *pBtr;
pBt->getAt(ACDB_MODEL_SPACE, pBtr, AcDb::kForRead);
pBt->close();

AcDbObjectId ObjId = pBtr->objectId();
AcDbIdMapping IdMap;
IdMap.setDestDb(pDb);
es = pDb->wblockCloneObjects(ObjIdArr, ObjId, IdMap, AcDb::kDrcReplace, false);


es = pBtr->close(); // es returing eNotHandled

es = pDb->saveAs(_T("c:/users/mypc/desktop/abc.dwg"));

delete pDb;

Fernando Malard said...

Hi,

Did you try to close the pBtr before doing the wblockCloneObjects()?

Further, did you debug and check all "es" values during the code execution?
What happens if you return without performing the wblockCloneObjects()? Does AutoCAD works ok?

Regards,

Anonymous said...

yes I tried pBtr before doing the wblockCloneObjects().
I have also checked all "es" values during the code execution and all are returning eOk except this one.

If I dont use wblockCloneObjects, the autocad works fine. In my present working dwg, two circles are drawn by autocad and also a new file is created named abc.dwg but the new file doesnot contain the two circles.

I guess I have not set my AcDbIdMapping method correctly. I also tried including the setting of original database as IdMap.origDb(pDB) which points to cuurent working database but still things doesnot workout.

Regards

Fernando Malard said...

I don't think you need to setup anything on AcDbIdMapping object. Just declare it and that's it.

Do you run anything else before this code?
Any chances you are leaving any object container opened for write?

Regards,

Anonymous said...

I got it. The problem was I was appending the circles at the wrong place.

Regards

Fernando Malard said...

Great, glad you solved this!

Unknown said...

Hi Fernando,

I want to know, how to get xData,attached to any component(entity) in ObjectArx by selecting each component of an assembly using selection set method. I want to read xData of each component and store it in a variable for further use. Can you suggest me any methods for the same using C++ code.

Thanks & regards

Fernando Malard said...

Hi Prashansa,

You can basically collect the entities you want, open each for read and use the method xData() to extract its XData information.

If you are looking for entities which already contain a specific application XData you can also use selection set filters to select only those entities containing that data.

Class8 can give you further info about selection sets: http://arxdummies.blogspot.com.br/2005/03/class-8-selection-sets.html

Regards,

Unknown said...

Hi Fernando,

Thank-you for the reply but I want to extract xData information of each entity which is already containing a specific application xData. I don't want to filter entities on the basis of xData.

Regards
Prashansa

Fernando Malard said...

One database object can contain multiple application xdata packages.
They are organized by the appname so when you are retrieving the xdata you need to provide the application name:

AcDbEntity* pEnt = NULL;
// somehow you will open the entity...
resbuf* xdata = pEnt->xData(_T("YOUR_DESIRED_APPNAME"));

This way, the resbuf will contain only data belonging to the application name provided.

Regards,

Unknown said...

Hello Fernando,

I got it. Now, I am able to get xData of all entities.

Thanks
Prashansa

Anonymous said...

Hi Fernando,

How do we cast an AcDbEntity into AcDb3dSolid using smart pointers?

Regards,

Fernando Malard said...

Hi,

You shouldn't cast the smart pointer, it is a template class.
Instead, you can retrieve the encapsulated AxRx pointer through object() method.
As this method will return the AcRx pointer you can then cast it to the class you want.

Keep in mind that you should not close or delete this pointer once it is controlled by the Smart Pointer wrapper.

Take a look at: AcDbSmartObjectPointer::object()

Regards,

Unknown said...

Hello Fernando,

I have a problem with delete an unused layer using object arx ?
I try call the function AcDbDatabase::purge(), this function returns Acad::eOk and only performs a check.
I create arrays objectID and push back all layerId in database, but when delete arr layerId (delete all layers (used + unused)).

Thank for any idea.
DDam

Fernando Malard said...

Hi Dam,

Firstly, please don't do multiple posts about the same question. This Blog requires every post to be approved before it is visible due spam attacks.

There is an article here explaining how to safely delete layers:

http://adndevblog.typepad.com/autocad/2012/12/finding-all-unreferenced-layers-using-objectarx.html

Hope it helps.

Anonymous said...

Hi Fernando,

I have object of Angular Dimension say AcDbDimension. I want to know which two lines are associated with the dimension object. Is there any member function to do that?

Thank you!

Fernando Malard said...

Hi,

I believe this is how the association process works:

- The entities the dimension is bound to stores a Reactor object pointing to AcDbDimAssoc
- This Reactor is stored into dimension's Extension Dictionary "ACAD_DIMASSOC"
- Whenever the associated entities change the reactor is fired and the dimension is updated

In order to find the associated entities you will need to go open dimension's extension dict "ACAD_DIMASSOC", get its AcDbDimAssoc object there and then use the method AcDbDimAssoc::getDimAssocGeomIds() to collect the ObjectIds of associated entities.

All this in theory as I never used myself this portion of the API.
Let me know if it works.

Regards,

Unknown said...

Hi Fernando,

I need to select the (line/circle/text) entities from Autocad drawing page and on selection want to invoke my callback function.
Could you please let me know any sample code to handle this.

Regards,

Fernando Malard said...

Hi Reshma,

The Lab1 sample does what you are asking:

http://arxdummies.blogspot.com.br/2005/03/lab-1-solved.html

Keep in mind that each entity will have its corresponding class. Circle maps to AcDbCircle, Line maps to AcDbLine, text maps to either AcDbText or AcDbMText.

Regards,

Reshma said...

Thank you Fernando for reply. Sorry I missed a point.
I need to do the selection of line using the mouse on the Drawing page of autocad,
on selection the callback function should get called with its coordinates passed to it.
SO need sample code for this case.
Regards,

Fernando Malard said...

You will need to implement an Editor Reactor and capture the pickFirstModified event:

AcEditorReactor::pickfirstModified()

It will fire whenever the user selects something before run a command.
Unfortunately I don't have a sample code for that.

Regards,

Reshma said...

Thank you Fernando.
I could get the control in pickfirstModified callback() on clicking the line entity,
now i need to fetch the line coordinates. Need pointers.

Regards,

reshma said...

HI Fernando,
By any chance do you have the code at below link.
objectarx2008\samples\editor\mfcsamps\pretranslate
If so, could you please share .
Thank you so much.

Fernando Malard said...

Sorry, I don't have this SDK anymore.
Did you try to contact Autodesk ADN?

Regards,