Saturday, February 05, 2005

Class 2 - AutoCAD's Database

Introduction

Each AutoCAD drawing represents a structured Database which stores several types of objects. When you just open a new drawing, AutoCAD creates behind the scenes a organized and efficient Database. This Database has a minimun data that allows you to make basic drawings.

This minimun data is represented basically by objects like layers, linetypes, text styles, etc. Due that you have layer 0, stadard text style, continuous linetype and others.
Since AutoCAD Release 2000 you can work with multiple drawings at the same time which is called MDI environment. This functionality brings great flexbility but also bring some extra complexity when dealing with more than one drawing. We won't discuss MDI aspects on this course but this will be probably a requirement for your future ObjectARX applications.

How data is stored

This Database mantains all sort of objects a drawing needs to exist. These objects are stored into appropriate containers which are special objects made to manage objects of the same type. This way we have appropriate methods and procedures to store entities, layers, text styles, etc.

Each object stored into Database receives an identifier that is called ObjectId. This identifier is unique inside the same AutoCAD session and it is valid during the lifecycle of each object. The ObjectId is generated by its Database and you don't need to worry about how to create it.

Inside ObjectARX we have basically 3 kind of objects:
  • Entities: Objects with graphical representation (lines, arcs, texts, ...);
  • Containers: Special objects to store and manage collections (layer table, linetype table, ...);
  • Objects: Objects without any graphical representation (groups, layouts, ...).



AutoCAD's Database structure

Creating objects

To create an object through ObjectARX we have some kind of recipe depending on what type of object it is and where we would like to store it (most of time we need to store an objects inside its specific container). Basically, you will follow this sequence:

  1. Declare a pointer to the object type you would like to create and call its new operator;
  2. With this pointer, call appropriate methods of this object to change its features;
  3. Get a pointer to the Database where you would like to create the object (most of time the current Database);
  4. Open the appropriate container where it should be stored;
  5. Call the specific container method to store your object passing its pointer;
  6. Receive its ObjectId automatically generated by its container;
  7. Finish the operation closing all opened objects including containers and the object you have just created.

Obviously you will create some handy classes to allow the automation of this processes because they are very similar and can be easily reused. The main idea is to create a sort of database utility funcions like: AddLayer, AddLine, AddCircle, AddTextStyle, etc.

* It is very important to not forget to close opened objects because this will cause AutoCAD to terminate.

Sample code to create a line (AcDbLine)

This code demonstrates how to create a simple line between two points. The process is simple and no error check is made. This code needs to be embedded inside an ObjectARX application structure to work. The main idea is to show you the concepts. Further we will create a working code. Pay attention to the order of opening and closing operations.



// We first need to declare a couple of points
AcGePoint3d startPt(1.0, 1.0, 0.0);
AcGePoint3d endPt(10.0, 10.0, 0.0);

// Now we need to instantiate an AcDbLine pointer
// In this case, its constructor allows me to pass the 2 points

AcDbLine *pLine = new AcDbLine(startPt, endPt);

// Now we need to open the appropriate container which is inside BlockTable
AcDbBlockTable *pBlockTable = NULL;

// First, get the current database and then get the BlockTable
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
pDB->getSymbolTable(pBlockTable, AcDb::kForRead);

// Inside BlockTable, open the ModelSpace
AcDbBlockTableRecord* pBlockTableRecord = NULL;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);

// After get ModelSpace we can close the BlockTable
pBlockTable->close();

// Using ModelSpace pointer we can add our brand new line
AcDbObjectId lineId = AcDbObjectId::kNull;
pBlockTableRecord->appendAcDbEntity(lineId, pLine);

// To finish the process we need to close ModelSpace and the entity
pBlockTableRecord->close();
pLine->close();



On the next class we will present the ObjectARX application structure and will build and compile a simple application using the above code. See you there!

49 comments :

Anonymous said...

Hello,
Should I delete the AcDbEntity pointer?

Anonymous said...

Why you don't close the AcDbDatabase pointer?

Fernando Malard said...

Hi there!

1)You only should delete those pointers you have allocate but not added to database. Once you have add an object to database AutoCAD take control of its lifecycle and you only need to close it.

2)The AcDbDatabase pointer is managed by AutoCAD and it stay opened as long as its associated drawing is available. You should only delete AcDbDatabase pointer when you have allocate it and it is not attached to any opened document inside AutoCAD application environment.

Anonymous said...

Hi Fernando,

Here is what I did base on your code sample.

// ----- lesqsimpleLine._MyLine command (do not rename)
static void lesqsimpleLine_MyLine(void)
{

acutPrintf("\nSimple Line Command in ObjectARX...");

int iRet;

AcGePoint3d startPt, endPt;

iRet = acedGetPoint(NULL , "\nFrom point:" , asDblArray(startPt) );

if (iRet == RTNORM)
{

iRet = acedGetPoint(asDblArray(startPt) , "\nTo point: " , asDblArray(endPt) );

if (iRet == RTNORM)
{
AcDbLine *pLine = new AcDbLine(startPt, endPt);

AcDbBlockTable *pBlockTable = NULL;

AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
pDB->getSymbolTable(pBlockTable, AcDb::kForRead);

AcDbBlockTableRecord* pBlockTableRecord = NULL;
pBlockTable->getAt(ACDB_MODEL_SPACE, pBlockTableRecord, AcDb::kForWrite);

pBlockTable->close();

AcDbObjectId lineId = AcDbObjectId::kNull;
pBlockTableRecord->appendAcDbEntity(lineId, pLine);

pBlockTableRecord->close();
pLine->close();

}

else { acutPrintf("\nNo second point selected..."); }

}

else { acutPrintf("\nNo first point selected..."); }

Fernando Malard said...

Hi,

I did not test your code but it looks good.

Keep improving!

BTW, I will request those users who would like to submit their improved samples and post into a user files folder.

Regards,
Fernando.

Anonymous said...

Great,

Just let me know where that folder is going to be please.


Gracias,
Luis.

Anonymous said...

Hi Fernando & Luis,

There are code fragements designed by you two in this section. Can anybody tell me which environment this code fit? is it a Visual C++ one?

Best Regards,

Yang

Fernando Malard said...

Hi Yang,

Yes, these code fragments are in C++ targeting ObjectARX 2004 and above.

You need to use VC++.NET 2002 to create ObjectARX applications compatible with AutoCAD 2004, 2005 and 2006.

Regards,
Fernando

Fernando Malard said...

Hello Rebekh,

Major ObjectARX contents on the Internet are placed into Forum Groups and Blogs. There are a couple of books about ObjectARX at Amazon.com

Only free sources I known is my Blog and Kean's Blog (http://through-the-interface.typepad.com/). VC++ information can be found at Microsoft MSDN website.

Hope this helps.
Regards.

Fernando Malard said...

Hi,

Take a look at Class3a. You need to setup VS to know both \inc and \lib ObjectARX paths.

Regards,
Fernando.

Anonymous said...

Hello

How can I acess, add or modify from program, entries made in Drawing Properties... on the Custom tab?

Regards

Fernando Malard said...

Hi,

There is a class to handle that. It is called AcDbDatabaseSummaryInfo and its pointer can be acquired with acdbGetSummaryInfo() global function.

If you need to access the same information from outside AutoCAD, there is an ActiveX control called DwgPropsX. Inside this control you will find several methods to put/get information.

Regards,
Fernando.

Anonymous said...

Hello

I have a class derived from AcDbDoubleClickEdit, and I have a class derived from AcDbEntity.

I use ...desc()->addX(AcDbDoubleClickEdit::desc() ... to add the double click reactor to the entity.

My goal is to modify some entity-s internal data in the database when the user double clicks on a specific entity.

But I can't open them in write mode, i get error eLockViolation .
Is there any way to overcome this obstacle?

Regards, Alonso

Fernando Malard said...

Hi Alonso,

If you are using AutoCAD 2007 the double click API is now part of CUI. Take a look at the .NET class:

Autodesk.AutoCAD.Customization.DoubleClickAction

If you are not using 2007, try to put the following code just before you open the entity for write:

AcApDocument *pDoc=acDocManager->curDocument();
acDocManager->lockDocument(pDoc,AcAp::kWrite);

After close the entity, unlock the document like this:

acDocManager->unlockDocument(pDoc);

Hope this help.
Regards.

Anonymous said...

Hello

Thx for the help, it really solved my problem.

Grateful, Alonso

Anonymous said...

Hello

I have an arx application and a custom menu wrote for AutoCAD 2006.

How can I load the custom menu from the application, without telling the user to use the AutoCAD command CUI?

Can you give me an example how to use DEMANDLOAD to set my arx application to load whenever AutoCAD starts?

I'm aiming to make an install kit for my program where I set everything, so the user simply starts AutoCAD and can use my menu and program.

Regards, Alonso

Fernando Malard said...

Hi Alonso,

The AutoCAD MENU and TOOLBARS can be manipulated directly through the COM API or through C# (.NET API). There is a sample at:

C:\ObjectARX 2007\samples\dotNet\CuiSamp

It shows how to create menus, toolbars, etc., using the .NET CUI API through C# (the same works if you use VB.NET).

Regarding to the demand loading process it is just a matter of registry entries. You can find more information about this on the ObjectARX SDK Help into "Demand Loading" topic.

Regards,
Fernando.

Sandip said...

please send simple code of vb to integrete autocad database
to get the parameter of line

Anonymous said...

Hello Fernando,

I have a question:

[code]
AcDbObjectId lineId=AcDbObjectId::kNull;

pBlockTableRecord->appendAcDbEntity(lineId,pLine);

[/code]

Why don't you use function: appendAcDbEntity(pLine) ??

Regards,
Duy

Fernando Malard said...

Hi Duy,

In fact you are right but I prefer to use this other signature version which returns the new entity's objectId because would be a good idea to check if it is valid.

I have removed a lot of code check on these samples to provide a clear code and an easy understanding.

It is really up to you to choose the right signature to use.

Regards,
Fernando.

Anonymous said...

Dear Fernando,

Please how to determine if DB pointer is still valid.

In my case I have two DWG for example dwg1 and dwg2 when close dwg2 the pointer is invalid after close document so case terminate of program.

So what is the solution?

Is the solution using Reactor? Please give me any code sample if answer is yes?

Best Regards,
M.S

Fernando Malard said...

Hi M.S,

It is not a good idea to keep the pointers of databases. The best approach is to monitor the documents through a document reactor and then get each document attached database pointer.

Take a look at the docman sample of ObjectARX SDK.

Regards,
Fernando.

Anonymous said...

Dear Fernando,

I have this code it work but when replace activateDocument with setCurDocument at line 12 and 16 this code does not work (Terminate program)
(This code activate second dwg if first dwg is active and vise versa)

AcDbVoidPtrArray *m_pDBArray = acdbActiveDatabaseArray();
if(m_pDBArray->length() == 2)
{
AcDbDatabase *pDb1 = (AcDbDatabase *)(m_pDBArray->at(0));
AcDbDatabase *pDb2 = (AcDbDatabase *)(m_pDBArray->at(1));

AcApDocument *pDoc0;
AcApDocManager *doc_manager = acDocManagerPtr();
pDoc0 = doc_manager->curDocument();

AcDbDatabase *pDbTemp0 = pDoc0->database();
If(pDbTemp0 == pDb1)
{
AcApDocument *pDoc;
pDoc = doc_manager->document(pDb2);
Acad::ErrorStatus error11= doc_manager->activateDocument(pDoc);
}
else if(pDbTemp0 == pDb2)
{
AcApDocument *pDoc;
pDoc = doc_manager->document(pDb1);
Acad::ErrorStatus error11= doc_manager->activateDocument(pDoc);
}
}
else
{
acutPrintf("\n Number of DWG less than 2");
}

Do you have any idea?

Regards,
M.S

Fernando Malard said...

Hi M.S,

Activate a document and make a document current are not the same thing. I would recommend you to read the ObjectARX documentation on the MDI section which explains in details what each concept is.

There are some restrictions that are valid to activate a document and others to set a document current.

Regards,
Fernando.

Jelena said...

Hi,

I have plane AutoCad 2008 and AutoCad Map 2009 on the same machine.

Arx's with the line

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

can not be loaded in 2008 "cannot find a procedure that it needs"
but well in Map

Any idea's?

Fernando Malard said...

Hi Jelena,

Really strange.
All 2009 based products should be binary compatible with 2008 compiled ObjectARX applications.

Did you test with a simple empty project created with ARXWizard?

Regards,

Jelena said...

Hi Fernando,

Empty project made with ArxWizard works fine. Although, other arx projects not involving the line with

"acdbHostApplicationServices()->workingDatabase();"

are also working fine (like your MinimumApp).

Temporary it’s no problem since I can work with Map.

I'll post if I find something on this matter.

shripad said...

Hi Fernando,
I have objectIDs of some drawn entities..I want to create block of that entities..What should I do??
Thanks in Advance..

Fernando Malard said...

shripad,

The process consist of two steps:

1) Create a BlockTableRecord to store the desired entities. At this step you will provide a unique name for the BlockTableRecord and will receive its ObjectId after created it.

// Get the block table.
AcDbBlockTable *pBlockTable = NULL;
acdbHostApplicationServices()->workingDatabase()->getSymbolTable(pBlockTable, AcDb::kForWrite);

// Create and name a new block table record.
//
AcGePoint3d oPt(0,0,0);

AcDbBlockTableRecord *pBlockTableRec = new AcDbBlockTableRecord();
pBlockTableRec->setName(_T("YourBlockName"));
pBlockTableRec->setOrigin(oPt);

// Add the new block table record to the block table.
//
AcDbObjectId blockTableRecordId;
pBlockTable->add(blockTableRecordId, pBlockTableRec);
pBlockTableRec->close();
pBlockTable->close();

2) With the BlockTableRecord ObjectId, use the deepCloneObjects() method as follows:

// This is the array of ObjectId you will obtain
AcDbObjectIdArray arrObjectID;

AcDbIdMapping idMap;
acdbHostApplicationServices()->workingDatabase()->deepCloneObjects(arrObjectID, blockTableRecordId, idMap);

This is the outline process. I have not tested it but it should work.

Regards,
Fernando.

Anonymous said...

Hello Fernando !
Im here to asking for helps or some suggestions plz !
I got an error when calling an compiled help files (.CHM) in AutoCAD 2000,it says "The file you called isn't a Windows help file or has been corrupt。

I got the same errors when i using .CHM files in AutoCAD 2005 or others

I've found the codes and tips in setfunhelp functions but this is not work anyways
==============================
(defun c:test ()
(getstring "Press F1 for help on the foo command:")
)
(setfunhelp "c:test" "Help.chm" "Test01")

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

On the other hand , the .hlp compiled files work properly when calling out in AutoCAD
==============================

Could it be done in ObjectARX ? Mr Fernando !

Fernando Malard said...

Hello,

Can you open this CHM file with a double click?

Windows consider CHM file as risky documents so when you try to open them from a network share or when you copy it from another computer it is locked by default.

If it is the case, you need to right click the file into Windows Explorer, go to its properties and unlock the file.

Regards,

Anonymous said...

hi fernando , i can open the CHM file with double click, but i can' found any properties setup or unlock likey options in any chm files, i guess there is format or support problem in windows xp


how do i fix the chm format problem, how do i updates them ?
Plz needs some advice .

Fernando Malard said...

Sorry, I have no clue about that.

One thing you can check is if the API you are using is compatible with the CHM version. One good test would be creating a standalone EXE application to open this CHM file. This will isolate the problem.

Regards.

Anonymous said...

void inserirBlocos(AcGePoint3d point, TCHAR *nomeBlock)
{
AcDbBlockTable *bt;
acdbHostApplicationServices()->workingDatabase()->getBlockTable(bt,AcDb::kForWrite);

AcDbBlockTableRecord *btr;
bt->getAt(ACDB_MODEL_SPACE, btr, AcDb::kForWrite);
bt->close();

AcDbBlockReference *br = new AcDbBlockReference(point, getIdBlockRec(nomeBlock));

AcDbObjectId idIns;
btr->appendAcDbEntity(idIns, br);

btr->close();
br->close();

}

and nothing appear on the screen!!

Fernando Malard said...

Hello,

First, you don't need to open the BlockTable for write to insert a block into the ModelSpace. Only the ModelSpace need to be opened for Write.

Second, if your method getIdBlockRec() try to open again the BlockTable for Write, it will fail once you already opened it for Write.

Third, the block must exist first into BlockTable before you can insert it into ModelSpace as a new BlockReference. I'm supposing your method create it.

Regards,

keshan_n said...
This comment has been removed by the author.
Fernando Malard said...

Keshan,

COPYCLIP is done via AcDbObject::wblockClone() where you will have access to the original object and the clone.

With this information you may block, tweak or update any information during clone process.

The second parameter is a pointer to the cloned object so I suppose if you delete it and pass back a NULL pointer it won't be cloned.

Haven't tried that but it should work.

Another possibility it to return something different from Acad::eOk as the ErrorStatus.

Let me know if it works.
Regards.

kirti said...

Hi,
Can you please tell me how to read existing dwg file?

Thank you in advance.

Kirti.

Fernando Malard said...

Hi kirti,

There is a sample under:

\ObjectARX 2010\samples\database\testdb_dg

It will show you how to do that.

Regards.

Anonymous said...

hey all , i want to disable wheel mouse double clicking by writing an arx or any other way , but i dont know how ! please can somebody help me ?

Fernando Malard said...

Hi, I have no idea how to do that.
Did you take a look at the acad.pgp customization file?
Have you tried the do it through CUI customization?

Regards,

Anonymous said...

Hello,
Why did you create an ObjectId to receive the new id if you won't use it?

Fernando Malard said...

Hello,

Actually AutoCAD database creates and returns the ObjectId when you append a new object to it.

The code just stores the returned value into a local variable. The ObjectId can be further used, into the same DWG session, to retrieve the Object pointer through a Open/Close operation.

It works like a database identification for the database client (our apps).

Regards,

Unknown said...

This is the command to refresh the data:
void RefreshDB()
{
resbuf *rb_in = acutBuildList(
RTSTR, _T("c:wd_mdb_rebuild"),
RTNIL,
RTNONE);
resbuf *rb_out;

acedInvoke(rb_in, &rb_out);

acutRelRb(rb_in);
acutRelRb(rb_out);
}

But I want to refresh the parameters in the WFRM2ALL table how to do this?

Fernando Malard said...

Vũ Đinh Văn,

I have never worked with AutoCAD Electrical specific customization. Not sure which specific command you need to use to access a specific table.

If you can do it via .NET I would recommend you to use LINQ expressions to access the data but I'm not sure whether the MDB data is exposed through the .NET API or not.

Did you try to post this question at the ObjectARX Developer group?

http://discussion.autodesk.com/forum.jspa?forumID=34

Regards,

Anonymous said...

Is there anyone who can provide arx & dll file that can change AutoCAD 2014 Title Bar.

For example I want only "AutoCAD 2014" to appear.

gpa@inbox.com

Fernando Malard said...

Hello gpa,

I have never tried that but assuming Windows API does provide you access to all windows it should be possible. One thing I'm pretty sure is that AutoCAD will update the title every time you change the DWG file so I'm not sure how complicated to monitor that and change the title when it happens.

Regards,

Windchill Blogspot said...

Hello Fernando Sir,

I need help regarding autoCAD custom Drawing property. I have developed code using ObjectARX
in vs2010 c++ for get drawing properties and modify custom property.
I have loaded .arx file in autocad. but i didn't get what command is used for to fetch drawing property.

Thanks in advance.

Fernando Malard said...

Hello Netaj,
When you create an ARX application you need to register commands to be called from AutoCAD prompt.
These commands will then running your code and performing the tasks you have setup.

Take a look at the documentation about acedRegCmds->addCommand() and acedRegCmds->removeGroup(). These methods are called during the application initialization method acrxEntryPoint() where you will register and unregister commands.

If you look at the ObjectARX SDK samples you will find these methods.

Regards,