Friday, May 27, 2005

Class 13 - Deriving from AcDbEntity

Introduction

On previous class you saw that we can derive from AcDbObject to create powerful custom objects and store them inside NOD. On this class you will see that we can also create custom entities, deriving from AcDbEntity or one of its derived classes, which will present you a new way of thinking about how much powerful an ObjectARX application can be.

At the launch of AutoCAD 13 ObjectARX SDK opened a new world for developers and for Autodesk itself. Custom entities allows developers to build rich graphical applications and present several new features not included into native AutoCAD entities. Autodesk also start to develop other applications based on new entities that brought great functionalities and unique experience.

When you think about a custom entity you need to first think on how it will be used, handled, edited and all features it will present to users. This will allow you to outline the custom entity behavior and list all tasks it will need to support and perform. This step is very important to decide if is better to develop a custom entity or if is better to use a native AutoCAD entity adding some data using XData or XRecords.

In other hand, when you use standard AutoCAD entities you will need also to handle its behavior to support and manage all things users may perform with it. This is not quite simple and depending on how creative are your users it will take you much time. When you adopt the custom entity solution it will give you much advantage on handling user operations but it will be more complex to implement.

Advantages on using custom entities

Several advantages will point you to the custom entities approach. I would like to list some of these advantages just to allow you to perceive how powerful it is:

Custom graphics: When you create your own custom entity you are responsible for its graphics. ObjectARX provides you some primitive drawing functions that allow you to draw your custom entity using everything you need. No matter if your entity is too simple or too complex, ObjectARX provides tools to do that.

Custom Grips and OSNAPs: It is also up to you implement or not Grips and Object Snap functionalities to your custom entities. You may need some specific OSNAP points and advanced Grip functions, go ahead, use them!

Custom transformation: Your custom entity can be transformed using your own criteria. This takes full advantage of powerful AcGe library included into ObjectARX.

Embedded native entities: If you would like to build an entity that looks like some native entities you can embed those entities into your custom entity and take advantage of all already implemented code. Suppose you need to build a custom entity that is much like a Polyline with a hatch inside. This can be easily done by embedding an AcDbHatch entity inside your AcDbPolyline derived custom entity.

Custom entity graphics

The AcGi library provides all you need to construct your custom entity's graphics. Basically your custom entity will be presented on AutoCAD screen using one or both of the following methods:

virtual Adesk::Boolean
AcDbEntity::worldDraw (AcGiWorldDraw * pWd);

virtual void
AcDbEntity::viewportDraw (AcGiViewportDraw * pVd);

The first function, called worldDraw(), is responsible to draw standard graphics for your entity. The second function, called viewportDraw(), is optional and it allows you to draw viewport dependent graphics. These functions receive a drawing context pointer that will allow you to perform your drawing as well.

These functions are called several times due several reasons and you need to provide the code inside them as faster and efficient as you can. Don't perform heavy calculations, long loops and other time consuming tasks there. If you are using native entities to draw you custom entity does not declare them inside these functions. Declare them as members of your class and just forward the calls inside worldDraw() or viewportDraw() to these embedded entities.

AutoCAD performs the drawing process walking through all database entities and calling first the worldDraw() method. If the worldDraw() method returns Adesk::kFalse, AutoCAD also walks through each viewport and call entity's viewportDraw() method. Exactly at this point you may draw a different graphic depending on each viewport configuration.

The provided drawing primitives are quite simple and I will just list them here: Circle, Circular arc, Polyline, Polygon, Mesh, Shell, Text, Xline and Ray. Please refer to the AcGi documentation inside ObjectARX SDK for instructions and detailed information on how to use them. Primitives are called using a geometry() function.

Custom entities allows you to also subdivide them. This feature is done using the AcGiSubEntityTraits object. The AcGiSubEntityTraits object sets graphical attribute values using the following traits functions:
  • Color
  • Layer
  • Linetype
  • Polygon fill type
  • Selection marker
This way you can separate logical information through its graphics. For instance, if you entity has some texts and some lines you may separate them into two different subentities. Further, if you would like to draw part of your entity using a specific color or linetype, this is also done using subentities. Each subentity could have its own mark. This will allow you do perform advanced interation with users by discovering on which subentity user has clicked. If you would like to do that, before draw your subentity graphics, give a call to setSelectionMarker() passing an incremental index.

Inside viewportDraw() function you will also have access to caller viewport information through AcGiViewport object inside the AcGiViewportDraw object passed in. The viewport geometry object provides the same primitives world geometry object plus the following polygon and polyline primitives, which use eye and display space coordinates: polylineEye, polygonEye, polylineDc and polygonDc. Some examples of both worldDraw() and viewportDraw() methods are presented below:

Adesk::Boolean MyEnt::worldDraw(AcGiWorldDraw *pW) {
AcGePoint3d verts[5];

// Create some random points
verts[0] = verts[4] = AcGePoint3d(-0.5, -0.5, 0.0);
verts[1] = AcGePoint3d( 0.5, -0.5, 0.0);
verts[2] = AcGePoint3d( 0.5, 0.5, 0.0);
verts[3] = AcGePoint3d(-0.5, 0.5, 0.0);

// Set the subentity color as 3

pW->subEntityTraits().setColor(3);

// Draw the polyline primitive

pW->geometry().polyline(5, verts);

return Adesk::kTrue;
}

void MyEnt::viewportDraw(AcGiViewportDraw* pV){
AcGePoint2d lleft, uright;

// Get viewport's DC coordinates

pV->viewport().getViewportDcCorners(lleft,uright);

// Perform some math here

double xsize = uright.x - lleft.x;
double ysize = uright.y - lleft.y;
xsize /= 10.0;
ysize /= 10.0;
double xcenter = uright.x - xsize;
double ycenter = uright.y - ysize;
double hxsize = xsize / 2.0;
double hysize = ysize / 2.0;


AcGePoint3d verts[5];
// Set vertex initial value
for (int i=0; i<5; i++) {
verts[i].x = xcenter;
verts[i].y = ycenter;
verts[i].z = 0.0;
}
// Perform some adjustments
verts[0].x -= hxsize;
verts[0].y += hysize;
verts[1].x += hxsize;
verts[1].y += hysize;
verts[2].x += hxsize;
verts[2].y -= hysize;
verts[3].x -= hxsize;
verts[3].y -= hysize;
verts[4] = verts[0];


// Set the subentity color as 3

pV->subEntityTraits().setColor(3);

// Draw the polyline on DC context

pV->geometry().polylineDc(5, verts);
}

Implementing Object Snap (OSNAP)

Your custom entity probably will need to provide some precision points through Object Snap feature. Depending on how much complex your custom entity is you will need to implement several OSNAP points and several types like EndPoint, Center, etc. To add OSNAP features to your custom entity you will need to add the following method to your class (there are other signatures):

virtual Acad::ErrorStatus AcDbEntity::getOsnapPoints(
AcDb::OsnapMode osnapMode,
int gsSelectionMark,
const AcGePoint3d& pickPoint,
const AcGePoint3d& lastPoint,
const AcGeMatrix3d& viewXform,
AcGePoint3dArray& snapPoints,
AcDbIntArray& geomIds) const;

This function will allow you to fill the passed in AcGePoint3dArray with those points that match with the provided osnapMode. Possible values to osnapMode are:

AcDb::kOsModeEnd: endpoint on the entity that is nearest to the pickPoint.
AcDb::kOsModeMid: midpoint (of any line, arc, etc., subentity) that is nearest to the pickPoint.
AcDb::kOsModeCen: center point (of any circle or arc subentity) that is nearest to the pickPoint.
AcDb::kOsModeNode: node point that is nearest to the pickPoint.
AcDb::kOsModeQuad: quad point that's nearest to pickPoint.
AcDb::kOsModeIns: insertion point of the entity (the insertion of a BlockReference or an MText object).
AcDb::kOsModePerp: intersection point of the entity and a line perpendicular to it passing through lastPoint.
AcDb::kOsModeTan: point on the entity where a line passing through lastPoint will be tangent to the entity
AcDb::kOsModeNear: Find the point on the entity that's nearest to pickPoint.

Imagine your custom entity is a rectangle and the user is running a LINE command and hover your entity with the EndPoint OSNAP enabled. Your entity will need to respond AutoCAD providing those points that could be used as EndPoints of your entity. In this case, inside your getOsnapPoints() function, you will need to fill the AcGePoint3dArray with the points of the rectangle corner. AutoCAD choose which of these points are inside the aperture box and the closest point to the cursor. So, your function will be something like:


Acad::ErrorStatus
MyEnt::getOsnapPoints(
AcDb::OsnapMode osnapMode,
int gsSelectionMark,
const AcGePoint3d& pickPoint,
const AcGePoint3d& lastPoint,
const AcGeMatrix3d& viewXform,
AcGePoint3dArray& snapPoints,
AcDbIntArray& geomIds) const {

assertReadEnabled();
switch (osnapMode) {

case AcDb::kOsModeEnd:
snapPoints.append(pt[0]);
snapPoints.append(pt[1]);
snapPoints.append(pt[2]);
snapPoints.append(pt[3]);
break;


}
return Acad::eOk;

}

The intersection OSNAP is not implemented through getOsnapPoints() method. As it is much more complex there is a special function called intersectWith() to handle that. I won't present details about this here but you may read and find further information inside ObjectARX SDK documentation.

Implementing GRIP and Stretch points

Grip points provides a great and simple way to user edit and transform entities. You will probably want to implement this feature for your entity. Further, stretch points will allow users to stretch your entity as well. These two features are very simple to implement and make your entity much more flexible and powerful.

Basically you just need to inform which are your key points to Grip and stretch. Other functions will be responsible on set the behavior of your entity when each Grip and stretch point are used. Depending on the complexity of your entity these functions may become a little bit complex.

For the Grip points feature you will need to implement a couple of functions. The first function, called getGripPoints(), will return those points where you would like to enable a Grip. The second function, called moveGripPointsAt() will perform the action when Grips are fired:

virtual Acad::ErrorStatus
AcDbEntity::getGripPoints (AcGePoint3dArray& gripPoints,
AcDbIntArray& osnapModes,AcDbIntArray& geomIds) const;

virtual Acad::ErrorStatus
AcDbEntity::moveGripPointsAt (const AcDbIntArray& indices,
const AcGeVector3d& offset);

Remember that Grip points don't need to be created only where there is a part of your graphics. You can, for instance, create a Grip at the center of a rectangle and there is nothing drawn there.

The getGripPoints() function receive 3 arguments. Currently only the first argument is used. It is an array of 3D points passed in by AutoCAD. This array contains all points involved into the current Grip operation. As other entities may be involved into this operation this array may already be filled. You just will append your desired points to this array. The points you have appended inside getGripPoints() will be identified by an index from the order they were appended to the point array.

The moveGripPointsAt() function will receive the index array and a 3D vector sent by AutoCAD with the current transformation (AcGeVector3d) being applied. At this time you just need to loop the index array, get each of its values (the index) and, depending on this value fire the transformation at the desired point. Imagine again your entity is a rectangle and you have 5 Grip points, one of each corner and one at the center. For each corner you will apply only the transformation to its grip and for the center grip you will apply the transformation to all of your points. The corner grip operation will result into a stretch at that corner and the center grip operation will result into a move of the whole entity. To apply the transformation to each point just call the transformBy() method passing in the AcGeVector3d received from AutoCAD.

In other hand, stretch points are defined and controlled by another two functions. They are much like the Grip functions and sometimes you just return a call to the Grip functions inside respective Stretch functions:

virtual Acad::ErrorStatus
AcDbEntity::getStretchPoints(
AcGePoint3dArray& stretchPoints) const;

virtual Acad::ErrorStatus
AcDbEntity::moveStretchPointsAt(
const AcDbIntArray& indices, const AcGeVector3d& offset);

The behavior of stretch functions are almost the same as the Grip functions and if your stretch points will behavior like Grip points you may just forward the call as below:

Acad::ErrorStatus MyEnt::getStretchPoints(
AcGePoint3dArray& stretchPoints) const {
AcDbIntArray osnapModes,geomIds;
return MyEnt::getGripPoints(stretchPoints,osnapModes,geomIds);
}

Acad::ErrorStatus MyEnt::moveStretchPointsAt(
const AcDbIntArray& indices, const AcGeVector3d& offset) {
return MyEnt::moveGripPointsAt(indices,offset);
}

The same concept of worldDraw() and viewportDraw() functions also applies here to the moveGripPointsAt() and moveStretchPointsAt() functions. They are called several times and they need to be as faster as you can. When you click at an entity's grip the moveGripPointsAt() function is called for every small mouse movement.

Implementing Transformation

Your custom entity needs to support transformation if you would like to allow users to perform commands like MOVE, ROTATE and SCALE. This feature is implemented through transformBy() method which receives a transformation matrix representing the current transformation being applied to your entity. Inside this function you will apply this matrix to your entity's data to reflect the modifications. The AcGeMatrix3d class support all types of transformations and encapsulate them through a matrix:

virtual Acad::ErrorStatus
AcDbEntity::transformBy(const AcGeMatrix3d& xform);


A typical implementation of transformBy() function could be:

Acad::ErrorStatus MyEnt::transformBy(
const AcGeMatrix3d& xform) {

pt[0].transformBy(xform);
pt[1].transformBy(xform);
pt[2].transformBy(xform);
pt[3].transformBy(xform);

}


In some special cases that I won't present here, you may need to apply the transformation to a clone or copy of your original entity. This is done using the getTransformedCopy() method which receives the transformation matrix and a pointer to be filled with the entity's transformed copy.

Too much information? Next class I will present a short and practical example with a custom entity. Stay tuned!

Monday, May 23, 2005

First ObjectARX for Dummies Contest

Hello,

I have some good news for you.
Recently came to my mind an idea to promote a contest for you but I thought it will much more interesting if I can offer some prizes.

The contest will go live after I present the class about custom entities and it will be about AutoCAD 2006 new features. Users will create ObjectARX samples using at least one of the new AutoCAD 2006 features. The prizes will be:

1st place: An AutoCAD 2006 NFR (Not for resale) full copy + Visual Assist X
2nd place: An AutoCAD 2006 NFR (Not for resale) full copy

The AutoCAD 2006 NFR copies will be provided by Autodesk (thanks to Shaan Hurley) and the copy of Visual Assist X will be provided by Whole Tomato software (thanks to Jeff).

All samples submitted by users will be made available at this site and the winners will be chosen by me and two additional people (I trying one people from Autodesk and one AutoCAD author).

Samples will be judged by its creativity and innovation. Remember that the contest is about AutoCAD 2006 new features and they must be the most important feature of your samples.

For now, start to study the ObjectARX 2006 SDK and how to use the its new features.
Stay tuned for the official contest start, rules and agreements.

Regards,
Fernando.

Friday, May 20, 2005

Lab 3 - Solved

Hello,

Here are the steps to build the sample:

-Create a Blank Workspace called Shapes;
-Add the two projects: ShapesUi (ARX) and ShapesObj (DBX). Both with MFC extensions;



-Create a dependency from ShapesUi to ShapesObj;
-Compile de project with Build All option;



-Open Autodesk Class Explorer and right click over ShapesObj project;
-Select "Add an ObjectDBX Custom Object" option;



-Enter the Class Name as ShapeObject. DWG protocol functions are automatically enabled. Click Finish;
-Two files will be created in your ShapesObj project: ShapeObject.h and ShapeObject.cpp;



-Open Autodesk Class Explorer again and right ShapeObject class. Select Add variable...;



-Enter the variable name (I suggest you to prefix all members with m_), enable "Implement Get/Put methods" option;
-Select "Access" as protected because we will use the Get/Put functions to change variables;
-Fill the "Comment" field with a description and uncheck the "Increase version number" option. Click Finish;

-Repeat this process for each of our variables: m_d, m_tw, m_bf, m_tf and m_desig;
-Go to the header file of your custom object class and make some adjustments to group variables and functions;



-Compile your project. You will get one error:
"cannot convert parameter 1 from 'CString *...".

This is due the problem when writing and reading CString types.
We need to make some changes into dwgInFields() and dwgOutFields() methods:

dwgOutFields:
from:

pFiler->writeItem(m_desig);

to:

pFiler->writeString(static_cast<const TCHAR*>(m_desig));

dwgInFields:
from:

pFiler->readItem(&m_desig);

to:

TCHAR* temp = NULL;
pFiler->readString(&temp);
m_desig.Format(_T("%s"),temp);
acutDelString(temp);

-Now, let's change the CString access functions:

from:

CString ShapeObject::get_m_desig(void) const
{
assertReadEnabled () ;
return (m_desig) ;
}

Acad::ErrorStatus ShapeObject::put_m_desig(CString newVal)
{
assertWriteEnabled () ;
m_desig =newVal ;
return (Acad::eOk) ;
}

to:

void ShapeObject::get_m_desig(CString& newVal) const
{
assertReadEnabled () ;
newVal.Format(_T("%s"),m_desig) ;
}

Acad::ErrorStatus ShapeObject::put_m_desig(LPCTSTR newVal)
{
assertWriteEnabled () ;
m_desig.Format(_T("%s"),newVal);
return (Acad::eOk) ;
}

This will avoid heap violation problems when passing strings from one
module to another. Remember to also change the respective function declarations.

- Open the acrxEntryPoint.cpp, inside ShapesUi project, and add
the following include:

#include "..\ShapesObj\ShapeObject.h"

- Now, we will use the Class 12 createMyObjects() and
listMyObjects()
functions to deal with our objects. Add these functions to
acrxEntryPoint.cpp file, before the application class and make proper
adjustments
. Remember that our application dictionary will be called "SHAPEAPP";

- Create two commands (CSHAPE and LSHAPE) and map each
one to above functions. Be creative, optimize your code as much as you can!

- Now, before create the research function we will need to add the ==
operator
to our custom object:

bool operator ==(const ShapeObject& arg) {
if (&arg == this) return true;

// We will need to use a tolerance as our values are double
// Let's use 0.1

if ( (fabs(m_d - arg.m_d ) < 0.1) &&
(fabs(m_tw - arg.m_tw) < 0.1) &&
(fabs(m_bf - arg.m_bf) < 0.1) &&
(fabs(m_tf - arg.m_tf) < 0.1) )

return true;
else
return false;
}


- To create a research function use the list function as a base and try to
find an existing shape before create a new one.

- See the solved sample code to all detailed solutions.

Remember, to test the application first load the DBX module and then the ARX.
To unload, first unload the ARX module then the DBX.

Download this sample's source code here!

See you next class!

Monday, May 09, 2005

User Samples

Hello,

I have received the first user sample today.
Thanks to Nikolay Poleshchuk from Russia.
His sample is called book9eng.zip and is available at OFCDesk ftp.
He is a LISP programmer and he is starting to learn ObjectARX.
www.cad.dp.ua/english/poleschuk_e.html
Keep sending me your samples!
Regards,
Fernando.

Sunday, May 08, 2005

Lab 3 - Creating a custom object

Hello,

Our third Lab will cover custom objects. You will need to create a custom object, its properties, functions and Dictionary related methods.

Requirements:

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

Objectives:

The main idea is to create the first part of a complete custom shape application. This part consist of building the custom object which will represent the shape form with properties that allows you to completely define it.

The second step will be the custom entity which will refer to its associated shape. As several entities may use the same shape it is not a good idea to repeat the shape information inside each entity. Exactly due that we will create this custom object which will store de shape information and share this information with all custom entities related to it.

It will be an "I" shape with equal top and bottom flanges and it will need to store the following information:

-web height (d) - double;
-web thickness (tw) - double;
-flange width (bf) - double;
-flange thickness (tf) - double;
-shape name (desig) - CString;

Instructions:

- You will create two projects. The first, called ShapesUi (which will be the ObjectARX module) and the second called ShapesObj (which will be the ObjectDBX module) using ObjectARX Wizard;
- Enable Using MFC option in both projects;
- After created both projects, open the Autodesk View toolbar , select the ShapesObj project and create the custom object called ShapeObject;
- Override filling functions (dwgOutFields / dwgInFields);
- Implement all necessary properties to this object and provide is persistence through DWG filling functions;
- Create two functions. One to create the object and stored it inside NOD and other to list all existing objects. Use your custom Dictionary as ShapeApp;
- Use "*" as the key name of each shape inside ShapeApp Dictionary;
- Create a research function which will receive the shape information and will lookup an existing shape that matches with the information. If it is found, return it's ObjectId instead of create a duplicated shape. If it is not found, create a new shape and return its brand new ObjectId;
- Don't forget to create a == operator for your class to make easy to compare shapes when researching the Dictionary;

Tips:

- Use the class 12 code fragment to help you with this project;
- Add properties using ARXWizard because it will create the access methods and will put the filling line at dwgInFields() and dwgOutFields() functions;
- Be careful about code flow inside Dictionary manipulation functions;
- Double check your open / close scopes;
- Test SAVE and OPEN the DWG to assert that the custom objects are successfully saved and loaded;
- Use ARXDBG to verify if your custom objects are placed into the right place;
- Use the ObjectARX documentation when necessary;

Support:

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

Expected time:

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

Saturday, May 07, 2005

Call for user samples

Hello,

I have received some messages from users about their samples and I would like to invite all users to post their samples at this Blog. This is a great opportunity to show yourself to other users and share your great ideas to solve common problems.

So, if you would like to do this you will need to proceed as follows:

1) Compact your sample project into a ZIP file (please exclude all Debug \ Release directories and all NCB files). This will reduce the file size significantly. Include a ReadMe.txt file in your sample root folder with the following information:

Author: Your name
Current version: number this version
Release history: list all releases and respective remarks
About this sample: Describe here the sample and what it does, steps to run, limitations, etc.

2) Send your sample and the above information to fpmalard@yahoo.com.br;

3) As soon as I read your e-mail I will post it on OFCDesk FTP area inside a UserSamples folder.

4) Don't be shame to send codes with errors or with incomplete logic. The main idea is to share your great ideas with others!

I'm waiting for your samples!
Regards,
Fernando.

Sunday, May 01, 2005

Class 12 - Deriving from AcDbObject

Introduction

I will start with Custom Objects and then proceed with custom entities on the next class. Custom objects can be used for several purposes and they are very powerful. Once your application creates and manage a custom object you will be able to construct complex application structures as well as much more intelligent and efficient data storage.

Starting with a simple example, suppose that you will need to build an ObjectARX application which implements some bars that has a length property and several types of shapes. Is possible that more than one bar has the same shape and it would be nice if you can provide a single instance of shape's information and share it among all bars using this shape.

The first impulse is to repeat the information on each bar no matter you will duplicate information. This works but will generate additional problems beyond the first problem which is the unnecessary space used to store the same information. Suppose that you need to update the shape and you would affect all bars that are using this shape. If your information is repeated in all bars you will need to open each bar and update its information. In other hand, if shape's information is stored into one single place and bars reflect this information you will need only to update this information in one place and all bars using this shape will be updated as soon as you update the shape's information.

AutoCAD use this technique on several features like layers, text styles, groups, etc. You will use exactly a custom object to store this information and share it, through its ObjectId, among all "clients" of this object.

How to begin

As discussed on previous class, you will need to derive from AcDbObject to be able to build your own custom object. This can be easily done using the ARXWizard or you can do by yourself creating the class by hand.

After create the class you need build some methods to create, store and acquire those objects instances from its container. Well, but where you should store your custom objects? AutoCAD provides a general purpose container called Named Object Dictionary (NOD). NOD is capable to store and persist any custom object derived from AcDbObject. It uses a dictionary like storage structure where you put a unique key (at the same level) and an object instance through its pointer and ObjectId. There are other custom object containers like Extension Dictionary that I will avoid due our course audience.

The NOD container could (and should) be organized by folders to make your dictionary as much organized as you can. The first node should be your application name to avoid conflict with other third-party ObjectARX applications that could use the NOD at the same time as you. The second level should contain all your business groups of objects. This will really depend on how many and the number of custom object types you have. NOD does not prohibit you to stored different classes at the same level but I really recommend you to avoid this except in case you need to stored generic objects together like on a Preferences group of objects.

You don't need to always Open and Close the NOD and go deep to find where are your desired object every time you need to access it. You can build some kind of cache of the most used Objects through its ObjectId and manage this cache to be updated for every single opened drawing. Remember that NOD is part of AcDbDatabase object and it is per document. So, you need to care about to build and fill your dictionary for every brand new drawing.

How to persist your custom objects

As I said before, the most used place to store custom objects is the NOD which is an AcDbDictionary. NOD takes care of its child objects because it is a container. So, when the AcDbDatabase object is issued to save its data by AutoCAD it also pass this message to its child objects and NOD is one of them. Once NOD receives this message it walks through its structure and call dwgOutFields() for every object stored there. The same process occurs when you open the drawing and the dwgInFields() is called by AcDbDatabase on NOD and consequently on its children. Exactly due that you will need to override the DWG filling methods to make possible to persist your custom objects among DWG open/close sessions.

Essential functions to override in your custom object class are:

virtual Acad::ErrorStatus dwgInFields(AcDbDwgFiler* filer);
virtual Acad::ErrorStatus dwgOutFields(AcDbDwgFiler* filer) const;
virtual Acad::ErrorStatus dxfInFields(AcDbDxfFiler* filer);
virtual Acad::ErrorStatus dxfOutFields(AcDbDxfFiler* filer) const;

If you don't plan to support DXF interface to your custom object you could avoid them.

Object's state management

On class 5 we have talked about object states when opening objects. Inside your custom object class you need to pay attention to call the proper assert method to make sure that all proper events and processes are fired when your object's state has changed. This is very important!

Those functions who change your object's data state must first call the assertWriteEnabled() function and then apply the required modifications. Functions who only read information from your object and does not affect its data state must call assertReadEnabled() function and also I really recommend that you make all these as const functions. This will avoid you to accidentally change the object's state when it is opened for read what will cause an assert error message. If you forget to call the proper assert method strange things may occur like call UNDO and your object stay unchanged and a lot other bizarre things.

How to create a custom object

To implement your custom object you will need to do the following:

1- Derive from AcDbObject;
2- Implement your data;
3- Implement access functions (read/write) with proper assert calls;
4- Implement the filling methods persisting and reading your data;

As a baseline, I will present a short example here:

// -------------------------------------------
// Class declaration
// -------------------------------------------

class MyClass : public AcDbObject {


public:
ACRX_DECLARE_MEMBERS(MyClass);

MyClass() {};
virtual ~MyClass() {};

Acad::ErrorStatus getVal (int& val) const;
Acad::ErrorStatus setVal (int val);

Acad::ErrorStatus getString (CString& str) const;
Acad::ErrorStatus setString (LPCTSTR str);

virtual Acad::ErrorStatus dwgInFields(AcDbDwgFiler*);
virtual Acad::ErrorStatus dwgOutFields(AcDbDwgFiler*) const;

private:
int m_Val;
CString m_Str;

};

// -------------------------------------------
// Class Definition
// -------------------------------------------

ACRX_DXF_DEFINE_MEMBERS(MyClass,
AcDbObject, AcDb::kDHL_CURRENT,
AcDb::kMReleaseCurrent, 0, MYCLASS, MYSAMP);

// -------------------------------------------
Acad::ErrorStatus MyClass::getVal (int& val) const {

assertReadEnabled();
val = m_Val;
return Acad::eOk;

}
// -------------------------------------------
Acad::ErrorStatus MyClass::setVal (int val) {

assertWriteEnabled();
m_Val = val;
return Acad::eOk;

}
// -------------------------------------------
Acad::ErrorStatus MyClass::getString (CString& str) const {

assertReadEnabled();
str.Format(_T("%s"),m_Str);
return Acad::eOk;

}
// -------------------------------------------
Acad::ErrorStatus MyClass::setString (LPCTSTR str) {

assertWriteEnabled();
m_Str.Format(_T("%s"),str);
return Acad::eOk;

}
// -------------------------------------------
Acad::ErrorStatus MyClass::dwgInFields(AcDbDwgFiler* pFiler) {

assertWriteEnabled();
AcDbObject::dwgInFields(pFiler);

Adesk::Int16 _val = 0;
pFiler->readInt16(&_val);
m_Val = _val;
TCHAR* _temp = NULL;
pFiler->readString(&_temp);
m_Str.Format(_T("%s"),_temp);
acutDelString(_temp);

return pFiler->filerStatus();

}
// -------------------------------------------
Acad::ErrorStatus MyClass::dwgOutFields(AcDbDwgFiler* pFiler) const {

assertReadEnabled();
AcDbObject::dwgOutFields(pFiler);

pFiler->writeInt16(m_Val);
pFiler->writeString(static_cast<const TCHAR*>(m_Str));

return pFiler->filerStatus();

}
// -------------------------------------------

// -------------------------------------------
// Entry Point
// -------------------------------------------
AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode msg, void* appId) {
switch (msg) {
case AcRx::kInitAppMsg:

acrxDynamicLinker->unlockApplication(appId);
acrxDynamicLinker->registerAppMDIAware(appId);

MyClass::rxInit();
acrxBuildClassHierarchy();

break;

case AcRx::kUnloadAppMsg:

deleteAcRxClass(MyClass::desc());

break;
}

return AcRx::kRetOK;
}

How to create and store your custom object

The NOD container is based on AcDbDictionary class which has several methods to read, write and erase entries. Your application needs to take care of NOD entries and be responsible to create instances of your custom class and store these objects inside the NOD. Each object stored must have a key defined or a generic key using the star * as its name.

void createMyObjects() {

AcDbDictionary *pNamedobj = NULL;
acdbHostApplicationServices()->workingDatabase()->
getNamedObjectsDictionary(pNamedobj, AcDb::kForWrite);

AcDbDictionary *pDict = NULL;
if (pNamedobj->getAt(_T("MYDICT"),(AcDbObject*&) pDict,
AcDb::kForWrite) == Acad::eKeyNotFound) {

pDict = new AcDbDictionary;
AcDbObjectId DictId;
pNamedobj->setAt(_T("MYDICT"), pDict, DictId);

}

pNamedobj->close();

if (pDict) {

MyClass *pObj1 = new MyClass();

pObj1->setVal(1);
pObj1->setString(_T("String1"));


MyClass *pObj2 = new MyClass();

pObj2->setVal(2);
pObj2->setString(_T("String2"));


AcDbObjectId rId1, rId2;

pDict->setAt(_T("*M"),pObj1, rId1);
pDict->setAt(_T("*M"),pObj2, rId2);


pObj1->close();
pObj2->close();


pDict->close();

}
}

How to verify if my objects are really stored inside NOD?

You will need to iterate the NOD entries to find your dictionary and then perform an iteration over its entries. The process should be something like this:

void listMyObjects() {

AcDbDictionary *pNamedobj = NULL;
acdbHostApplicationServices()->workingDatabase()
->getNamedObjectsDictionary(pNamedobj, AcDb::kForRead);

AcDbDictionary *pDict = NULL;
pNamedobj->getAt(_T("MYDICT"), (AcDbObject*&)pDict,AcDb::kForRead);
pNamedobj->close();

if (pDict == NULL) {

acutPrintf(_T("\nThe dictionary MYDICT does not exist. Please create it first!"));
return;

}

AcDbDictionaryIterator* pDictIter= pDict->newIterator();

MyClass *pMyClass;
int _val;
CString _str;

for (; !pDictIter->done(); pDictIter->next()) {

pMyClass = NULL;
pDictIter->getObject((AcDbObject*&)pMyClass,AcDb::kForRead);

if (pMyClass != NULL) {

pMyClass->getVal(_val);
pMyClass->getString(_str);
pMyClass->close();
acutPrintf(_T("\nMyClass: val=%d, str=%s"),_val,_str);

}

}

delete pDictIter;
pDict->close();

}

Stay tuned for the next Lab which will require you to build a custom object. See you there!