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.

32 comments :

Anonymous said...

Hola Fernando,

I would like to iterate the database using something like:
AcDbDatabase* pDb = curDoc()->database();
AcDbBlockTableRecordPointer modelSpace(ACDB_MODEL_SPACE,
pDb,
AcDb::kForWrite);

Then using the set class std::set

Make a LineSet... do you know how?

For example I know now how to do it for a point object

AcDbObjectPointer AcDbPoint pointObj(objId, AcDb::kForRead);

Then inside of the for loop I need to pass or add the line into a resbufer, but have no idea on how to verify if it is a line

For a point I can do this:
head = acutNewRb(RT3DPOINT);

Hope you have a sample code for it.... and after getting all lines, how about for Arcs, LwPolylines, Circles, etc.


Thank you,
Luis.

Fernando Malard said...

Luis,

I really don't understand what do you need. Maybe you can explain what you would like to do instead of describe your code fragment.

Some questions:
-Why do you want to iterate the whole ModelSpace?
-Do you want to open entities grouping them by its type and then build collections of entities?

Regards,
Fernando.

Anonymous said...

Hi Fernando,

Can I sent to you the code I have, with that I could explain myself better, I'm trying to write a drawing cleaner, to delete duplicate points, lines, arcs... etc.

As part of my arx training

Also, if you can provide examples on how to build collections of diferent objects types, I want to know too.

Best regards,
Luis.

Fernando Malard said...

Hi Luis,

Please send me your code to see if I can help you.

Regards,
Fernando.

Anonymous said...

Hi,
I use this code to iterate and print the xref name and the full path of the attached drawing file.

static void GetAllXrefs(void)
{
Acad::ErrorStatus es;
AcDbXrefGraph graphXref;
if ((es = acedGetCurDwgXrefGraph(graphXref)) == Acad::eOk) {
acutPrintf("\nTotal: %d xrefs",graphXref.numNodes()-1);
for (int idx=1; idx < graphXref.numNodes(); idx++) {
AcDbXrefGraphNode *nodeXref = graphXref.xrefNode(idx);
if (nodeXref) {
AcDbObjectId btrId = nodeXref->btrId();
AcDbBlockTableRecordPointer pBTR(btrId,AcDb::kForRead);
const char *path = NULL;
if ((es = pBTR.openStatus()) == Acad::eOk && (es = pBTR->pathName(path)) == Acad::eOk) {
acutPrintf("\n Xref[%d]=%s path=\"%s\"",idx,nodeXref->name(),path);
}
}
}
}
}

but i got this error


error C2065: 'AcDbBlockTableRecordPointer' : undeclared identifier

error C2146: syntax error : missing ';' before identifier 'pBTR'

error C2065: 'pBTR' : undeclared identifier

error C2228: left of '.openStatus' must have class/struct/union type


error C2227: left of '->pathName' must point to class/struct/union



I am new to ARX so pleas guide me how to resolve this.

Thanks in Advance
Patro

Fernando Malard said...

Hello Patro,

You are missing the following include file:

#include "dbobjptr.h"

AcDbBlockTableRecordPointer is a smart pointer and its definition is located inside this header file.

Hope this help.
Fernando Malard.

Anonymous said...

Hi Fernando Malard,
Thanks! it worked out.

After Getting the fullpath i need to delete the current drawing file , which is active i am using the following code.

AcApDocument* pDoc = acDocManager->curDocument();

acDocManager->closeDocument(pDoc);

CString toOpen("C:\\Temp\\56.dwg");

ShellExecute(NULL,"open",toOpen,NULL,NULL,SW_HIDE);

int p,g;
p = _unlink("C:\\Temp\\50.dwg");

but it says

AutoCAD canot close "C:\\Temp\\50.dwg", because there is a command still active. Please complete the command and try again.



if i remove the two line of the code

CString toOpen("C:\\Temp\\56.dwg");

ShellExecute(NULL,"open",toOpen,NULL,NULL,SW_HIDE);

and try then it is not able to delete the current file even i close the document. I think it is still locked.No idea.....
Please help.
Thanks in Advance
Patro

Fernando Malard said...

Hi Patro,

The problem is that your command is not running on application context to you are trying to close the document where your command is running. This is causing the error message.

First your command needs to be registered with ACRX_CMD_SESSION flag.

I suggest you to close the document via AutoCAD ActiveX interface to avoid time-response problems during closing process. Another option is to delete it from a document reactor callback.

To close via ActiveX, do something like:

#import "acad.tlb" no_namespace
void CloseDoc(const char *pFilename)
{
::CoInitialize(NULL);

CLSID clsid;
CLSIDFromProgID(L"AutoCAD.Application.15", &clsid);

IAcadApplicationPtr pAcadApp = NULL;
pAcadApp.GetActiveObject(clsid);
IAcadDocumentsPtr pDocs = NULL;
IAcadDocumentPtr pDoc = NULL;
pAcadApp->get_Documents(&pDocs);

long nDocs = pDocs->Count;
for(--nDocs;nDocs >=0;nDocs--)
{
pDoc = pDocs->Item(_variant_t((long) nDocs));
if (strcmpi(pFilename,(char *)(_bstr_t)pDoc->FullName) == 0)break;
}

if(pDoc != NULL) pDoc->Close();
CoUninitialize();
}

Next, inside your command function, do something like:

char *pFilename = new char[strlen(acDocManager->curDocument()->fileName())];
strcpy(pFilename,acDocManager->curDocument()->fileName());

CloseDoc(pFilename);

// Open a new doc
acDocManager->appContextOpenDocument("C:\\Temp\\56.dwg");

//delete the closed doc
int p,g;
p = _unlink("C:\\Temp\\50.dwg");

Let me know if this works for you.
Regards,
Fernando.

Anonymous said...

looks for corehdr.pdb

when i cancels.

it is breaking
at

if(pDoc != NULL) pDoc->Close();
CoUninitialize();
gives exceptions

is it that I need to do something with activex(no idea of activeX)

or some header file missing?
Regards
Patro

Fernando Malard said...

Patro,

The best way to solve that is isolate the problem. Can you build a sample only with the doc functionalities?

This way you can send it to me to help you.

My personal e-mail is fpmalard@yahoo.com.br

Regards,
Fernando.

Anonymous said...

i have sent the code with workspcae to ur yahoo id. Waiting for ur reply

Thanks in Advance
Patro

Anonymous said...

// SampleObjectArx.cpp : Initialization functions


#include "StdAfx.h"
#include "StdArx.h"
#include "resource.h"
#include afxdllx.h
#include vector

#include "xgraph.h"
using std::string;
using std::vector;
#include "dbobjptr.h"


HINSTANCE _hdllInstance =NULL ;

// This command registers an ARX command.
void AddCommand(const char* cmdGroup, const char* cmdInt, const char* cmdLoc,
const int cmdFlags, const AcRxFunctionPtr cmdProc, const int idLocal = -1);


// NOTE: DO NOT edit the following lines.
//{{AFX_ARX_MSG
void InitApplication();
void UnloadApplication();
//}}AFX_ARX_MSG

// NOTE: DO NOT edit the following lines.
//{{AFX_ARX_ADDIN_FUNCS
//}}AFX_ARX_ADDIN_FUNCS


////////////////////////////////////////////////////////////////////////////
//
// Define the sole extension module object.
AC_IMPLEMENT_EXTENSION_MODULE(SampleObjectArxDLL);

// Now you can use the CAcModuleResourceOverride class in
// your application to switch to the correct resource instance.
// Please see the ObjectARX Documentation for more details

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point
extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
_hdllInstance = hInstance;
// Extension DLL one time initialization
SampleObjectArxDLL.AttachInstance(hInstance);
InitAcUiDLL();
} else if (dwReason == DLL_PROCESS_DETACH) {
// Terminate the library before destructors are called
SampleObjectArxDLL.DetachInstance();

}
return TRUE; // ok
}

void GetAllXrefDatabases(AcDbXrefGraphNode* pNode, vector string & xRefFileNames, vector AcDbDatabase* & databases);

BOOL isItAnAssembly()
{

// Description: This function calls GetAllXrefDatabases() which looks for each
// node(xref) attached to the Host dwg. Also it looks through all the attached
// dwgs Xrefs(if there are any). Then it prompts the user for the document to
// save the key to.

//get the graph of Xrefs (this graph contains all the nodes(Xrefs))
AcDbXrefGraph xgraph;
Acad::ErrorStatus as = acedGetCurDwgXrefGraph(xgraph);
if (as != Acad::eOk)
return NULL;

//get the host databases node
AcDbXrefGraphNode* pHostNode = xgraph.hostDwg(); //this should always succeed
if (!pHostNode)
{
return NULL;
}

//get all the Xrefs attached to the Host database
vector string xRefFileNames;
vector AcDbDatabase* databases;
GetAllXrefDatabases(pHostNode, xRefFileNames, databases);

return false;
}
void GetAllXrefDatabases(AcDbXrefGraphNode* pNode, vector string & xRefFileNames, vector AcDbDatabase* & databases)
{
// Description: This function looks for each node(xref) attached to the Host dwg.
// Also it looks through all the attached dwgs Xrefs(if there are any).
Acad::ErrorStatus es;

if(pNode)
{
pNode->markAs(AcDbGraphNode ::kVisited);

databases.push_back(pNode->database());

xRefFileNames.push_back(pNode->name());

{
AcDbObjectId btrId = pNode->btrId();
AcDbBlockTableRecordPointer pBTR(btrId,AcDb::kForRead);
const char *path = NULL;
if ((es = pBTR.openStatus()) == Acad::eOk && (es = pBTR->pathName(path)) == Acad::eOk) {
acutPrintf("\n Xref[]=%s path=\"%s\"",pNode->name(),path);
}
}

}
//Iterate through all Xref databases looking for the requested key
for(int i = 0;i < pNode->numOut();++i)
{
AcDbXrefGraphNode* pOutNode = static_cast AcDbXrefGraphNode* (pNode->out(i));
if (pOutNode)// && !pOutNode->isMarkedAs(AcDbGraphNode ::kVisited))
{
GetAllXrefDatabases(pOutNode, xRefFileNames, databases);
}
}

return;
}

void connectMode()
{
TRY
{

AcApDocument* pDoc = acDocManager->curDocument();
char *pFilename = new char[strlen(acDocManager->curDocument()->fileName())];
strcpy(pFilename,acDocManager->curDocument()->fileName());

isItAnAssembly();// doing my operations
acDocManager->closeDocument(pDoc);
_unlink(pFilename);// Then deleting the opened file

}
CATCH(COleDispatchException,e)
{
e->ReportError();
e->Delete();
}
END_CATCH;
return;
}

/////////////////////////////////////////////////////////////////////////////
// ObjectARX EntryPoint
extern "C" AcRx::AppRetCode
acrxEntryPoint(AcRx::AppMsgCode msg, void* pkt)
{
switch (msg) {
case AcRx::kInitAppMsg:
// Comment out the following line if your
// application should be locked into memory

acedRegCmds->addCommand( "RX_TEST", "Mx", "Mx",
ACRX_CMD_MODAL, &connectMode);


acrxDynamicLinker->unlockApplication(pkt);
acrxDynamicLinker->registerAppMDIAware(pkt);
InitApplication();
break;
case AcRx::kUnloadAppMsg:
UnloadApplication();
break;
}
return AcRx::kRetOK;
}

// Init this application. Register your
// commands, reactors...
void InitApplication()
{
// NOTE: DO NOT edit the following lines.
//{{AFX_ARX_INIT
//}}AFX_ARX_INIT
// AfxMessageBox("");
// TODO: add your initialization functions

}

// Unload this application. Unregister all objects
// registered in InitApplication.
void UnloadApplication()
{
// NOTE: DO NOT edit the following lines.
//{{AFX_ARX_EXIT
//}}AFX_ARX_EXIT

// TODO: clean up your application
}

// This functions registers an ARX command.
// It can be used to read the localized command name
// from a string table stored in the resources.
void AddCommand(const char* cmdGroup, const char* cmdInt, const char* cmdLoc,
const int cmdFlags, const AcRxFunctionPtr cmdProc, const int idLocal)
{
char cmdLocRes[65];

// If idLocal is not -1, it's treated as an ID for
// a string stored in the resources.
if (idLocal != -1) {

// Load strings from the string table and register the command.
::LoadString(_hdllInstance, idLocal, cmdLocRes, 64);
acedRegCmds->addCommand(cmdGroup, cmdInt, cmdLocRes, cmdFlags, cmdProc);

} else
// idLocal is -1, so the 'hard coded'
// localized function name is used.
acedRegCmds->addCommand(cmdGroup, cmdInt, "mypoint", cmdFlags, cmdProc);
}
This is my code
Regards
Patro

Fernando Malard said...

Here are the fix steps:

// PATRO: Here we will import AutoCAD ActiveX interface typelibrary
#import "acad.tlb" no_namespace

// PATRO: This function uses the AutoCAD ActiveX interface to close the document
void CloseDoc(const char *pFilename)
{
// Startup COM Interface
::CoInitialize(NULL);

CLSID clsid;
CLSIDFromProgID(L"AutoCAD.Application.15", &clsid);

IAcadApplicationPtr pAcadApp = NULL;
IAcadDocumentsPtr pDocs = NULL;
IAcadDocumentPtr pDoc = NULL;

pAcadApp.GetActiveObject(clsid);
pAcadApp->get_Documents(&pDocs);

long nDocs = pDocs->Count;
for(--nDocs;nDocs >=0;nDocs--) {
pDoc = pDocs->Item(_variant_t((long) nDocs));
if (strcmpi(pFilename,(char *)(_bstr_t)pDoc->FullName) == 0)break;
}

if(pDoc != NULL) pDoc->Close();
// Shutdown COM Interface
::CoUninitialize();
}

// PATRO: Your command need to be ACRX_CMD_SESSION (application context)
acedRegCmds->addCommand( "RX_TEST", "Mx", "Mx",ACRX_CMD_MODAL | ACRX_CMD_SESSION, &connectMode);

I will send you the fixed project by e-mail.

Regards,
Fernando.

Anonymous said...

Thanks a lot Fernando, it worked out for me. I was struglling in this for a long. Hope in future I will trouble you like this to learn more from you.
Thanks!
Patro

Anonymous said...

"MDT Assembly files and MDT Model files are getting recognized, But MDT Drawing files are not getting recognized as MDT Drawing type. How to differenciated between them.


i am using this function to recognize between Assembly and Model

BOOL isAnAssembly()
{
Acad::ErrorStatus es;

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

// Create Iterator to search block table for xref objects

AcDbBlockTable* pBlkTbl;

es = pData->getBlockTable(pBlkTbl, AcDb::kForRead);

if (es != Acad::eOk)
{return 0;}

AcDbBlockTableIterator *pBlkTblIter = NULL;

es = pBlkTbl->newIterator(pBlkTblIter);

pBlkTbl->close();

if (es != Acad::eOk)
{return 0;}

BOOL flagSta = false;

AcDbBlockTableRecord* pBlkTblRec;

// Step thru base drawing block table looking for xrefs.

for (; !pBlkTblIter->done(); pBlkTblIter->step())

{


es = pBlkTblIter->getRecord(pBlkTblRec, AcDb::kForRead);

if(pBlkTblRec != NULL)

{

if((pBlkTblRec->isFromExternalReference() == Adesk::kTrue) )
{
flagSta = true ;
}

pBlkTblRec->close();

}

}
delete pBlkTblIter;
return flagSta;
}

Waiting for your help.

Thanks in advance
Patro.

Fernando Malard said...

Hi Patro,

Your routine actually is verifying only if a BlockTableRecord comes from an External Reference which does not specify it is an MDT assembly.

I guess a simple way do determine if a DWG is an MDT like file is to search for exclusive MDT information like some NOD entry or a special class.

If you further investigate MDT SDK you will probably find something simple to check if a DWG comes from MDT.

Regards,
Fernando Malard.

Bordei said...

Hello Fernando!
In the current AutoCAD working session, I try to create some block references starting from some curves selected in the current document.
This block I like to save and after reopening the file to be able to edit. The problem is that, editing is depending on the previous selected curves. In this moment I have implemented a dictionary, exactly like Step 5 - Custom Objects - Extension Dictionary - ArxLab example. It is possible to retain the selected curves in some way in this dictionary for a future reusing (in my case editing)? If yes , what is recommended way to do this. Save the curves handle in a string variable of the dictionary? Could you indicate an example? Or just indicate some steps to follow?

Kind regards!
Ioan

Fernando Malard said...

Ioan,

The Dictionary is not an entity container. Maybe the best solution is to create a dummy BlockTableRecord to keep those entities but this does not make sense once you already have the same block you have just created.

Maybe the best approach is to create the first block as some type of template and the next blocks will be created cloning this template.

Regards.

Bordei said...

Hello Fernando!
Thank you for this extremely fast response!
I do not intend to retain anything in the dictionary except some block parameters and THE CURVES (probably retaining their handle) which generate my blocks.
The problem is that I do not have any example how to retain these original curves, which is the starting point of block creation and convert theirs handles into a form to be written into my dictionary (i think I have to transform the handle into a string to write and when read back into a handle - int).
The second problem is that i do not not how to distinguish from saved handles.. It seems they are not unique!.. from Arx doc: "Handles are not unique across databases, however. In fact, duplication across databases is almost a certainty, since all databases start with the same handseed value of 1 and go up from there.
"

Any idea!

Kind regards!
Ioan

Fernando Malard said...

Ioan,

1)The curves must be inside some container (a BlockTableRecord) to be saved within the DWG database. This can be the ModelSpace or any other BlockTableRecord like a commom user defined block;

2)If you save de AcDbObjectId inside a Dictionary entry it will be translated by AutoCAD automatically. You don't need to care about this but they will be valid only if the curve still exist inside some BlockTableRecord;

3)If the curve data can be stored as numeric values and you don't need the entity itself you can store this data using a XRecord or even your own custom AcDbObject derived class. If you need the curve entity data you will need to store them inside a container.

4)Another solution to consider is to store the curves into an external DWG file and read it as necessary to create the new blocks.

Hope this help.
Regards.

Bordei said...

Hello Fernando!
I'm sorry because I do not make myself clear from the beginning: I try to start to explain from the beginning to understand what I want:

Step 1: I have some lines in AutoCAD on the screen. After I select these lines, I generate a block (this block is different from the initial curves, so on one hand there are initial curves , on the other hand now I have this new created block, composed of course from the lines too).

Step 2:
Save and close the file

Step 3:
Open the previous file and try to edit the block. In order to edit the block, I have to know about the lines from which this block was created in the first step

In the dictionary I do not save the block entities, of course. In this dictionary I try to save some parameters of the block. One of the parameters is a form to express this array of curves (lines) from AutoCAD which generate my block. These curves (or handles) need to be saved in order to be able to edit the blocks in a future document opening's. I need these curves, because the blocks are generated from them (there is a direct dependency between lines and block), and future editing of them start from these curves.

So I have 2 problems in this moment:
1. how to save these curves
2. Non unique HANDLES of the curves.

The block represent a tool path for CNC machines, and the generated curves is what is needed to be machined! I would like to be able to modifiy the tool path parameters (for example - tool radius) anytime is necessary! My dictionary save exactly these kind of informations but I need to save in it in some way the initial curves too.

Hope to be more clear now!
Kind regards!

Ioan

Fernando Malard said...

Ioan,

1)If those lines are preserved inside the DWG there is no problem. Create your own AcDbObject derived class, add an AcDbObjectIdArray of these lines as a member of this class and add it to the dwgIn/Out filer.

2)Anytime you need to access these lines just get the ObjectId and open. As I said, once you put the AcDbObjectId into the filer loop AutoCAD will take care of update them when the corresponding curves have their ObjectId changed too.

3)If the original curves will not kept into the same DWG you will need to create another block...say the template block and build your new block based on this original one. Once those lines inside the template block are inside the same database the AcDbObjectIdArray solution will work too.

Hope you got it now.
Regards.

Bordei said...

Hello Fernando!
Thank you, I think we are very close to the answer!
The original curves and the generated block are in the same dwg block, always, of course!
How could I "add it to the dwgIn/Out filer", a AcDbObjectIdArray data member array?
I'm just asking a simple example of the syntax, I could not find an example in documentation for using AcDbObjectIdArray, or an array in dwgIn/Out filer..

Thank you again!
Ioan

Fernando Malard said...

Ioan,

Add an AcDbObjectIdArray to your custom AcDbObject derived class. Your custom class will have its filer methods as described on class 12.

Inside these methods you need to walk through each element of this array and save each one as an AcDbSoftPointerId:

// Declare your array
AcDbObjectIdArray _arrCurves;

// Inside dwgOutFields()
Adesk::Int16 sz = _arrCurves.length();
pFiler->writeInt16(sz);
for (int i=0; i < sz; i++)
{
AcDbSoftPointerId id = _arrCurves[i];
pFiler->writeSoftPointerId(id);
}

// Inside dwgInFields()
Adesk::Int16 sz = 0;
pFiler->readInt16(&sz);
_arrCurves.setPhysicalLength(0);
for (int i=0; i < sz; i++)
{
AcDbSoftPointerId id;
pFiler->readSoftPointerId(&id);
_arrCurves.append((AcDbObjectId)id);
}

As you see it is necessary to save the length of you array because each ObjectId is saved individually.

As I said, AutoCAD takes care of the ObjectId translations.

Regards.

Estudiante said...

Hey, I'd like to add points to my drawing, how can I do that? I've tried with ::AcDb3dPolylineVertex but I don't think it's the right way.
Look, this is my code:

::AcGePoint3d punto(15, 12,0)
// ::AcDb2dVertex* pVertex = new ::AcDb2dVertex(punto);
::AcDb3dPolylineVertex* pPolyVertex = new ::AcDb3dPolylineVertex(punto);

AcDbBlockTable* pBlock = NULL;
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
pDB->getSymbolTable(pBlock,AcDb::kForRead);
AcDbBlockTableRecord* pBTR = NULL;
pBlock->getAt(ACDB_MODEL_SPACE, pBTR, AcDb::kForWrite);
pBlock->close();
// now, add the entity to container
AcDbObjectId Id;
pBTR->appendAcDbEntity(Id, pPolyVertex);
pBTR->close();
pPolyVertex->clone();


But it doesn't work, I doesn't show a point... What am I doing wrong...


Currently I'm doing a workaround

acedCommand(RTSTR, L"Point", RTSTR, dato, RTNONE);

But it's really slow when I'm adding 11,000 points..

By the way, thank you for your website... it's very good for beginners like me...

Fernando Malard said...

Hello,

You need to use AcDbPoint class.
Only AcDb prefixed classes can be added directly to a BlockTableRecord like ModelSpace.

The Vertexes classes like those you have tried are internal objects of their parent classes like Polyline.

Regards.

Estudiante said...

Fernando,

Thank you very much for your quick response. I did expect an answer however I didn't know it'd be that quick. Thank you very much.
I've been using ObjectARX for only 2 days so, I'm glad you don't mind answering simple questions like mine hehe..

-E.P.

Estudiante said...

Fernando, I did as you suggested and it's working perfectly, so fast!!

Fernando Malard said...

Great, thank you.

Don't remember to fix the close method call:

pPolyVertex->clone();

The method is CLOSE not CLONE.

Regards.

Estudiante said...

I did fix it, it's working perfectly! Thanks...

jyothi said...

I have closed polyline and I want to know how it was drawn clockwise or counterclockwise order (in other words what order points are returned by coordinates property)

Fernando Malard said...

Hello jyothi,

You will need to implement a Polygon Winding Algorithm.

There are several papers at the Internet about this, for example:

http://www.engr.colostate.edu/~dga/dga/papers/point_in_polygon.pdf

Regards.