ObjectARX & Dummies

Course Index

*Course support: Click here!
*Course and User samples: Download here!

Friday, January 12, 2007

Exercise2 - Step6

Creating a Custom Entity – Exercise 2 – Step 6

On this step we will implement a pretty nice feature. Imagine you would like to add a hatch filling to your custom entity. We can take advantage of ObjectARX embedded object feature to implement this. There is a class called AcDbHatch which represents the AutoCAD hatch entity. This class can be used as an embedded object and we can use its worldDraw() method to draw our own hatch pattern. The first thing you need to do is to add an AcDbHatch member to our custom entity’s class. We will also declare the SetupHatch() method to setup the hatch properties. To do that, open the AuPolyline.h file, and place the following lines at the end of class declaration:

protected:
AcDbHatch m_Hatch;

public:
void SetupHatch();

Further, we will need to add 3 more methods to our entity to handle modifications. The first method will handle all graphic transformations. The 2 remaining methods will handle the STRETCH command:

public:
virtual Acad::ErrorStatus transformBy(const AcGeMatrix3d & xform);
virtual Acad::ErrorStatus getStretchPoints(
AcGePoint3dArray & stretchPoints) const;
virtual Acad::ErrorStatus moveStretchPointsAt(
const AcDbIntArray & indices, const AcGeVector3d & offset);


Our hatch object needs to be configured. To do this we will place, inside the custom entity’s constructor located at AuPolyline.cpp file, the following code (note that this configuration needs to be done only once so the constructor is the better place to put it):

AuPolyline::AuPolyline () : AcDbPolyline ()
{
m_Hatch.setNormal(AcGeVector3d::kZAxis);
m_Hatch.setElevation(this->elevation());
m_Hatch.setAssociative(true);
m_Hatch.setPatternScale(1.0);
m_Hatch.setPatternAngle(45.0);
m_Hatch.setHatchStyle(AcDbHatch::kNormal);
m_Hatch.setPattern(AcDbHatch::kPreDefined,_T("LINE"));
}

This configuration will set the hatch pattern, normal vector, elevation, scale, angle and style. In this example they are fixed but you may want to create one property for each of these parameters allowing the user to change them at runtime.
Now we need to add the SetupHatch() method implementation to build the hatch loop according to our polyline boundary. The code will be as follows:

void AuPolyline::SetupHatch()
{
assertWriteEnabled();
// Remove previous loop
for (int l=0; l<m_Hatch.numLoops(); l++)
m_Hatch.removeLoopAt(l);
// Insert the updated loop
AcGePoint2dArray vertexPts;
AcGeDoubleArray vertexBulges;
// Collect points and bulges
for(int i=0; i<numVerts(); i++) {
AcGePoint2d pt2d;
double bulge = 0.0;
this->getPointAt(i,pt2d);
this->getBulgeAt(i,bulge);
vertexPts.append(pt2d);
vertexBulges.append(bulge);
}
// Close the loop
vertexPts.append(vertexPts.first());
vertexBulges.append(vertexBulges.first());
m_Hatch.appendLoop(AcDbHatch::kDefault, vertexPts, vertexBulges);
// Refresh hatch
m_Hatch.evaluateHatch();
}

On lines 05-06 we make sure there is no previous loop inside hatch. At the line range 11-18 we walk through the polyline vertexes and collect its points and bulges (the bulge is the tangent of 1/4 of the included angle for the arc between the selected vertex and the next vertex). The collected information will be stored at two dynamic vectors: AcGePoint3dArray and AcGeDoubleArray. On lines 20-21 we close the polyline loop to ensure our hatch boundary is closed.
On line 22 we append the arrays to the hatch entity as one loop. The loop can be also a hole into the hatch surface but in this example our loop is AcDbHatch::kDefault. On line 24 we finish the hatch configuration process by calling the evaluateHatch() method which will generate the hatch itself.
We need to call the SetupHatch() method inside some of our methods. The first place is inside the dwgInFields(). Place a call to this method at the end of this method as follows:

Acad::ErrorStatus AuPolyline::dwgInFields (AcDbDwgFiler *pFiler)
{
[ some lines were not displayed for code brevity ]
// Setup hatch
SetupHatch();
return (pFiler->filerStatus ()) ;
}

Next, we need to place another call inside moveGripPointsAt() method. When user moves some of the GRIP points we need to recalculate the hatch boundary. We need to do this only in cases the selected GRIP is not our center point. The change is made on lines 15-18 as follows:

Acad::ErrorStatus AuPolyline::moveGripPointsAt (
const AcDbVoidPtrArray &gripAppData,
const AcGeVector3d &offset, const int bitflags)
{
assertWriteEnabled () ;
for (int g=0; g<gripAppData.length(); g++)
{
// Get grip data back and see if it is our 0 Grip
int i = (int)gripAppData.at(g);
// If it is our grip, move the entire entity. If not, forward the call
if (i == 9999)
this->transformBy(offset);
else
{
AcDbCurve::moveGripPointsAt (gripAppData, offset, bitflags);
SetupHatch();
}
}
return (Acad::eOk);
}

To make the hatch entity appear as part of our custom entity’s graphics we need to call its worldDraw() method from inside our entity’s worldDraw():


Adesk::Boolean AuPolyline::worldDraw (AcGiWorldDraw *mode)
{
[ some lines were not displayed for code brevity ]
// =======================================================
// HATCH
m_Hatch.worldDraw(mode);
//------ Returning Adesk::kFalse here will force viewportDraw() call
return (Adesk::kTrue) ;
}

Finally, we need to implement the code for the 3 new methods we have added to our custom entity’s class. Open the AuPolyline.cpp file and add the following methods:


// -------------------------------------------------------------------------
Acad::ErrorStatus AuPolyline::transformBy(const AcGeMatrix3d & xform)
{
Acad::ErrorStatus retCode = AcDbPolyline::transformBy (xform) ;
m_Hatch.transformBy(xform);
return (retCode) ;
}
// -------------------------------------------------------------------------
Acad::ErrorStatus AuPolyline::getStretchPoints(AcGePoint3dArray & stretchPoints) const
{
AcDbIntArray osnapModes,geomIds;
return this->getGripPoints(stretchPoints,osnapModes,geomIds) ;
}
// -------------------------------------------------------------------------
Acad::ErrorStatus AuPolyline::moveStretchPointsAt(
const AcDbIntArray & indices,
const AcGeVector3d & offset)
{
Acad::ErrorStatus ret = AcDbPolyline::moveGripPointsAt (indices, offset);
SetupHatch();
return ret;
}

The first method on lines 02-07, transformBy(), is responsible for all entity’s graphic transformations such as MOVE, ROTATE, SCALE, etc. First we forward the call to our base class and then apply the same transformation to the hatch. This way it will follow all transformations applied to our AuPolyline.
The second method on lines 09-13, getStretchPoints(), is responsible to return the points that are enabled to stretch the entity. In this case we would like to add all of our polyline vertexes. This method can reuse the getGripPoints() method which returns the same points we want.
The last method on lines 15-22, moveStretchPointsAt(), is responsible to apply the stretch transformation over the entity. We will also reuse the existing method moveGripPointsAt() because it does exactly what we need. Next we just need to call SetupHatch() again to ensure our hatch is updated with the new boundary resulting from the STRETCH command.
Before test our custom entity, we need to place a call to SetupHatch() just before to close the entity on its creation stage. Open the acrxEntryPoint.cpp file, of AuUserInterface project, and locate the AuUserInterface_MyCommand1() method. See the code below:


static void AuUserInterface_MyCommand1(void)
{
[ some lines were not displayed for code brevity ]
pL->SetupHatch();
pL->close();
}

Now Build you Solution again. You should get no errors. Open AutoCAD, load your modules (remember, first BDX and then ARX). Fire MYCOMMAND1 command and create one AuPolyline entity. Next test the several features we have implemented. Try to COPY you entity, MOVE it, ROTATE it, SCALE it and apply a MIRROR. You can also use the STRETCH command and move the GRIP points to change the entity’s shape.

Our AuPolyline entity is derived from AcDbPolyline, right? So do you expect that a specific polyline commands like PEDIT work with our entity? Yes, it works! Try to fire the PEDIT command and select our polyline. It will accept it and will allow you to change the AuPolyline as if it is a native AutoCAD Polyline. You can add new vertexes, remove existing, join new segments and even open the polyline (Figure 19). Great!


Figure 19 – AuPolyline modifications.

Conclusion
In this session you have learned how to create a custom entity and add some of the possible features ObjectARX allows. This is really only the tip of the iceberg. There are much more you can do using ObjectARX classes and implementing more sophisticated features. I really hope you have enjoyed this session and hope it may help you to make the startup into the ObjectARX world.

Labels: , ,

Exercise2 - Step5

Creating a Custom Entity – Exercise 2 – Step 5

Sometimes you need to distribute an AutoCAD drawing with custom entities inside. By default, when AutoCAD opens a drawing and find some entity it does not recognize, it protects this entity and packs its binary data into a Proxy entity. The proxy entity protects your object data avoiding unwanted users to manipulate your custom entities.
The proxy entity is merely a dummy entity with a fixed graphical representation. Several features of your custom entity will not be available once your code is not there to provide these methods. If your drawing is opened by an unadvised user that would be nice if you inform this user about the missing application.
The proxy can contain specific graphics which will be generated with a call to your worldDraw() method just before AutoCAD close the drawing. The worldDraw() method has a parameter, an AcGiWorldDraw pointer, that allows you to call a regenType() method to find out if the caller is requesting proxy graphics to your entity. At this time, you can draw a different graphic to make and advertisement of your missing custom entity. The following code shows how to handle the proxy graphics:



// ==================================================================
// PROXY
if (mode->regenType() == kAcGiSaveWorldDrawForProxy)
{
// Draw dummy text
CString strTxt = _T("AU Polyline");
AcGePoint3d ptTxt = GetPolylineCenter();
mode->geometry().text(ptTxt, AcGeVector3d::kZAxis, AcGeVector3d::kXAxis, szRef, 1.0, 0.0, strTxt);
}

On this example the proxy graphics will be the standard entity graphic plus a text indicating our class name. You may also add an URL address of your product or company. We will use the center point as this text’s start point.
Unfortunately this solution is not complete for AcDbPolyline derived entities. There is a problem when a polyline needs to generate its proxy graphics and the worldDraw() method is not called. To solve this problem we need to add another method, called saveAs(), to our class. This method has the following declaration:

virtual void saveAs(AcGiWorldDraw * mode, AcDb::SaveType st);
The implementation of this method is as follows:

void AuPolyline::saveAs(AcGiWorldDraw * mode, AcDb::SaveType st)
{
AcDbPolyline::saveAs (mode, st) ;
if ((mode->regenType() == kAcGiSaveWorldDrawForProxy) &&
(st == AcDb::kR13Save))
this->worldDraw(mode);
}

This method first forward the call to our base class and then test if the regenType() and the save type is AcDb::kR13Save. If they are, we forward the call to our own worldDraw() method which will draw the custom entity graphic plus the proxy text message.
To test this behavior create some AuPolyline entities, save the drawing, close AutoCAD and then open this DWG without loading the application modules. Once the DWG file is opened you will see a proxy warning dialog with some information about the missing application (Figure 18).


Figure 18 – Proxy Information dialog.


Note on the Figure 18 that our proxy text is displayed and also, at the proxy information dialog, you may find information about the missing application. This dialog allows users to choose one of the 3 options about how AutoCAD will handle its proxy entities. You can also setup the standard proxy behavior accessing Tools > Options > Open and Save.
Remember that the DBX module can be loaded to re-enable the custom entity or you can also send your DBX module along with the DWG file to enable third-party users to see your custom entity with complete information. This way you can provide a full graphical representation without the application interfaces (ARX module). The third-party users will be able to see it but can’t modify the custom entity.
AutoCAD allows some basic operations over the proxy entity like erase, change layer, change color and transformations. These operations will act basically over its dummy graphical representation (they will not affect your custom entity’s data except when it is erased). It is up to the developer to determine the most adequate flags for each custom entity. This is a compiler-time option and it is defined on the custom entity’s class implementation macro (ACRX_DXF_DEFINE_MEMBERS). This flags can be combined to build a complete configuration. Please refer to ObjectARX SDK documentation for further information about proxy flags.

Labels: , ,

Exercise2 - Step4

Creating a Custom Entity – Exercise 2 – Step 4

Now you will learn how to add OSNAP points to your custom entity. If you run the application before this step you will see that our custom entity already shows some Object Snap points by default (ENDPOINT and MIDPOINT). In this example we want to add a CENTER object snap to our polyline that will allow users to select the exact center point. To provide this, we need to change the standard behavior of getOsnapPoints() method. This method has 4 signatures and 2 of them, containing the AcGeFastTransform parameter, are for future use. We will use the first signature and will handle the CENTER object snap responding with our center point:

Acad::ErrorStatus AuPolyline::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::kOsModeCen:
snapPoints.append(GetPolylineCenter());
break;
}
return (AcDbPolyline::getOsnapPoints (osnapMode, gsSelectionMark, pickPoint, lastPoint, viewXform, snapPoints, geomIds)) ;
}
This method receives an Array of points where we need to add the center point whenever the OSNAP CENTER is requested. On line 13 we handle the CENTER object snap by adding our center point to the snapPoints array (Figure 17). Even handling this particular object snap this method needs to call the polyline class level. The polyline will handle all other possible object snaps for us.


Figure 17 – CENTER object snap.

Note: This OSNAP solution handles only simple OSNAP types like CENTER, MIDPOINT and ENDPOINT. Complex OSNAP modes like INTERSECTION require other methods implementations and some additional procedures.

Labels: , ,

Exercise2 - Step3

Creating a Custom Entity – Exercise 2 – Step 3

Now you will learn how to add GRIP points to your entity. In fact, as the default implementation of AuPolyline forward the call to its base class you may already noted that its GRIP points are visible and working. The GRIP point behavior is handled by two methods. The first method, called getGripPoints() is responsible for acquiring all GRIP points from your entity. The second, called moveGripPointsAt(), is responsible for the action fired by each grip.
Each default polyline GRIP action is to move the related vertex. In this example we want to add one extra grip positioned at our polygon’s center. To do that we first need to create a helper method to calculate this point. We will walk through all polygon points and will sum the coordinates dividing the result by numVerts(). This method will not change our entity data so it is recommended to be CONST and will require only the assertReadEnabled() call:

AcGePoint3d AuPolyline::GetPolylineCenter() const
{
assertReadEnabled();
AcGePoint3d ptC,pti;
double cx = 0.0, cy = 0.0, cz = 0.0;
for (int i=0; i<numVerts(); i++)
{
this->getPointAt(i,pti);
cx += pti[X];
cy += pti[Y];
cz += pti[Z];
}
cx = cx / numVerts();
cy = cy / numVerts();
cz = cz / numVerts();
ptC.set(cx, cy, cz);
return ptC;
}


Lines 04-15 apply the center formula and at line 16 we build the point using cx, cy and cz as the point’s X, Y and Z coordinates respectively.
Next we need to change the default implementation of the both GRIP point methods. First we will redefine the getGripPoints() method. This method has 2 signatures and we will use the new AcDbGripData method instead of the “old style” method.
This method receives an array of AcDbGripData pointers. These objects represent the GRIP point information. We need to inform at least the point and an arbitrary data (void*). This arbitrary data can be used later to get back information from each GRIP point allowing the moveGripPointsAt() method to perform the custom actions:

Acad::ErrorStatus AuPolyline::getGripPoints (
AcDbGripDataPtrArray &grips,
const double curViewUnitSize,
const int gripSize,
const AcGeVector3d &curViewDir,
const int bitflags) const
{
assertReadEnabled () ;
AcDbGripData* gpd = new AcDbGripData();
gpd->setAppData((void*)9999); // Center Grip code
gpd->setGripPoint(GetPolylineCenter());
grips.append(gpd);
AcDbPolyline::getGripPoints (grips, curViewUnitSize, gripSize, curViewDir, bitflags);
return (Acad::eOk);
}


This method will not change our entity’s data so it is also a CONST method and calls assertReadEnabled() method. On lines 09-12 we instantiate an AcDbGripData pointer and set its application data (9999 in this case) and the grip point which is calculated by the GetPolylineCenter() method. Next, on line 13 we forward the call to AcDbPolyline’s grip point method so it is able to add its own GRIP points at last. These GRIP points are those vertexes points we have mentioned before. The next step is to change the moveGripPointsAt() method so when the user clicks on this center GRIP it will reflect a custom action. In this example, our custom action will move the entire polyline. The following code shows how to do that:

Acad::ErrorStatus AuPolyline::moveGripPointsAt (
const AcDbVoidPtrArray &gripAppData,
const AcGeVector3d &offset,
const int bitflags)
{
assertWriteEnabled () ;
for (int g=0; g<gripAppData.length(); g++)
{
// Get grip data back and see if it is our 0 Grip
int i = (int)gripAppData.at(g);
// If it is our grip, move the entire entity. If not, forward the call
if (i == 9999)
this->transformBy(offset);
else
AcDbCurve::moveGripPointsAt (gripAppData, offset, bitflags);
}
return (Acad::eOk);
}


This time, our method cannot be CONST once we will change our entity’s data. Due that, we need to call assertWriteEnabled() at it’s beginning. On line 07 we start to inspect the AcDbVoidPtrArray (an array of void*) looking for our application data (9999). This method also receives a 3D vector which represents the transformation being applied to the GRIP. If the GRIP being modified is our 9999 we will apply this vector transformation to the entire polyline. This can be done through the transformBy() method passing the same vector (Figure 16).


Figure 16 – Center GRIP in action.


If the fired GRIP is not our 9999 code we will forward the call to AcDbCurve class (AcDbPolylin’s base class) which will handle it for us. The resulting behavior is when you select a GRIP over the polyline boundary the entity stretches and when you select the center GRIP the entity moves.

Labels: , ,

Exercise2 - Step2

Creating a Custom Entity – Exercise 2 – Step 2

Now after you have created a basic custom entity you will learn how to add custom graphics to it. The custom entity’s graphics is generated primarily by the worldDraw() method and optionally by the viewportDraw() method.
As this entity’s base class already generates a polyline graphic we will add some nice extra graphics to it. Each vertex will receive an index number and an arrow indicating the polyline construction direction (Figure 14).
We will use different colors for each graphic type. The index number will use the 256 color code which is the ByLayer color and the arrows will use a fixed 1 color code which is red. If you change the color of this entity’s layer it will change except on the arrows that will stay red. Further, all arrows will be filled.


Figure 14 – Custom Entity with vertex numbers and arrows


To create these graphics you will need to use some trigonometric methods. The following code demonstrates what you can reach this:


Adesk::Boolean AuPolyline::worldDraw (AcGiWorldDraw *mode)
{
assertReadEnabled();
// Call base class first
AcDbPolyline::worldDraw(mode);
double szRef = 5.0;
// ================================================================
// DIRECTION AND VERTEX NUMBERING
int signal = 1;
double ht2 = szRef/4.0;
for(int i=0; i<numVerts(); i++)
{
AcGePoint3d pti;
this->getPointAt(i,pti);
// Draw vertex text
CString strNum;
strNum.Format(_T("%d"),i);
AcGePoint3d ptTxt = pti + (AcGeVector3d::kXAxis*ht2) + (AcGeVector3d::kYAxis*ht2);
mode->subEntityTraits().setColor(256); // ByLayer
mode->geometry().text(ptTxt, AcGeVector3d::kZAxis, AcGeVector3d::kXAxis, ht2, 1.0, 0.0, strNum);
/
/ Arrow direction
AcGePoint3d ptj;
this->getPointAt(i<(numVerts()-1) ? (i+1) : 0, ptj);
A
cGeVector3d dir = (ptj - pti).normalize();
// Side perpendicular vectors
AcGeVector3d perp = dir;
perp.rotateBy(3.141592/2.0,AcGeVector3d::kZAxis);
AcGePoint3d pt1 = ptj - (dir*ht2) + (perp*(ht2/4.0));
AcGePoint3d pt2 = ptj - (dir*ht2) - (perp*(ht2/4.0));
AcGePoint3d pts[3];
pts[0] = ptj;
pts[1] = pt1;
p
ts[2] = pt2;
// Draw arrow polygon
mode->subEntityTraits().setFillType(kAcGiFillAlways);
mode->subEntityTraits().setColor(1); // red
mode->geometry().polygon(3,pts);
mode->subEntityTraits().setFillType(kAcGiFillNever);
}
//------ Returning Adesk::kFalse here will force viewportDraw() call
return (Adesk::kTrue) ;
}

On line 03 we call the proper assert method which inform what type of access we are executing on this method. As we are not changing any data we need only to READ the entity so we call assertReadEnabled() method. On line 05 we forward up the call to its base class method which will draw the polyline curves. On lines 06-10 we initialize some local variables.
Next, from line 11-39 we perform a loop on each polyline vertex to draw our custom graphics. On lines 13-14 we get the current vertex point. On line range 16-20 we draw the vertex text using the text() geometry primitive at a point with a small displacement related to the vertex point. On lines 22-24 we calculate the end point (ptj) of each polyline segment and its unitary direction vector (dir). Next, on lines 26-33 we calculate the 3 points which will build an arrow head graphic. The perp vector will allow us to draw each side of the arrow head according to the Figure 15. On lines 35-38 we draw the filled arrow head using the polygon() primitive with color red and with the fill type always.

Finally, on line 41, we return Adesk::kTrue to avoid the graphics to be made by the viewportDraw() method.
These classes we have used, with AcGe prefix, are part of the AcGe library which contains several utility classes and methods to help us to deal with geometric calculations. This really helps a lot once most of these calculations are a little bit complex.


Figure 15 – AuPolyline side graphics.

Labels: , ,

Exercise2 - Step1

Hello,

I will post a sequence of 6 steps of my Exercise 2 provided at last year's AU. It will show you how to create a custom entity inside AutoCAD 2007 using ObjectARX 2007 and Visual Studio 2005.

Hope you enjoy these posts!

Creating a Custom Entity – Exercise 2 – Step 1

You will learn in this exercise how to create a simple custom entity. I will keep it simple as much as I can to reinforce the basic concepts involved. On the next steps we will improve this custom entity by adding great features step by step.
The first step is to name our entity and choose from which base class it will derive. Remember we have to split our application into two modules, an ARX (user interfaces) and a DBX (the custom entity class itself). These two projects will be placed into the same Visual Studio Solution and the ARX module will depend on DBX module. The names will be:





  • Solution: Exercise2 (Visual Studio will create a Exercise2.sln file);

  • DBX Project: AuCustomObjects (Visual Studio will create a AuCustomObjects.vcproj file);

  • ARX Project: AuUserInterface (Visual Studio will create a AuUserInterface.vcproj file);

Open Visual Studio, go to the menu File > New Project… Open “Other Project Types” node, click on “Visual Studio Solutions” item. It will show a template called “Blank Solution”. Choose this template and name it as Exercise2 (the location can be any folder you want). Click OK to proceed (Figure 8).
Next, right click the solution icon; select Add and then New Project…, (Figure 9). The dialog, presented on Exercise1 will appear. Select on the list the ObjectARX template and create both AuUserInterface and AuCustomObjects modules. Remember to choose ARX or DBX project type accordingly.





Figure 8 – Blank Solution project

Each project location will be created inside the existing solution by default. Don’t change this location.
Enable MFC option on both projects (don’t need to enable AutoCAD MFC extensions) Enable _DEBUG symbol too. Don’t enable any COM or .NET feature in both projects.
Make AuUserInterface project depend on AuCustomObjects. To do that, right click project AuUserInterface and select Dependencies… Then mark AuCustomObjects project on the list. Click OK.


Figure 9 – Creating solution’s projects

Right click project AuUserInterface again and select “Set as Startup Project” (it will turn to bold). Now test your solution building it: go to menu Build > Build Solution. You should get 2 Builds with 0 errors and (1+3) warnings that are safe to ignore.
The final test at this stage is to load the application inside AutoCAD. Remember our project AuUserInterface depends on AuCustomObjects so the DBX module needs to be loaded first than the ARX module. The unload process must be done in reverse order, AuUserInterface first and then AuCustomObjects. You should get everything working this way.
Note: Depending on which type of build you made (Debug or Release) it is recommended to load both projects with the same compilation type.
Now we have our DBX and ARX module it is time to add our custom entity’s class. In this example, our entity will be derived from AcDbPolyline which represents the AutoCAD POLYLINE entity. The reason for this option is that our custom entity will behave almost exactly like a polyline but it will add some extra features like vertex numbers, direction symbols, hatch, etc. To add this custom class we will use the Autodesk Class Explorer tool which is located at ARXWizard’s toolbar. It is the second button like the Firgure 10 shows.


Figure 10 – ARXWizard toolbar

Once you click on this button a dialog bar will appear allowing you to explore all existing classes into your projects. At this time, there are no custom entity classes available. Select our DBX module and then right click on it. A pop-up menu will be displayed. Select the “Add an ObjectDBX Custom Object” option (Figure 11).


Figure 11 – Autodesk Class Explorer

This wizard has 3 steps. The first step, called Names (Figure 12), allows you to specify all basic custom entity’s features. First, name it as “AuPolyline”. Choose as base class the AcDbPolyline class (note you have several other classes available to derive from). Remaining fields will be filled automatically. Press Next.
Once you select the polyline as your base class you are saying that your custom entity will behave like a polyline except where you redefine it. This is done through the virtual methods I have mentioned before.


Figure 12 – Custom Object Wizard - Names

The Custom Object Wizard also will help you to implement basic class features. This is done on the second step of this Wizard, called Protocols. Enabling these options will instruct the wizard to add the related virtual methods simplifying the class creation process for you.

Inside this dialog you can specify if your entity participate on DWG/DWF protocols, if your entity implements OSNAP points, GRIP points and viewport dependent graphics and, at last, if it implements curve protocols. In this example we will enable only DWG protocol and all 3 AcDbEntity protocols which will allow us to implement some basic features (Figure 13).
The third step, called Advanced, will allow you to add notification and cloning features to your custom entity. In this example we will not use these features. There are many other features you can redefine through a huge set of virtual methods but to keep this example simple we will only implement these ones.
Click Finish to create your Custom Entity’s Class. Remember, it will be placed at you DBX module.


Figure 13 – Custom Object Wizard - Protocols

The Custom Entity Wizard will create default virtual methods but, in this particular case, our base class AcDbPolyline does not support all of these methods signatures. If you compile the solution you will get 4 error messages exactly due that. For now, we will replace the explicit AcDbPolyline:: prefix on these 4 return calls by AcDbCurve:: prefix which is the AcDbPolyline base class. Now you should get no errors.
If you inspect your projects Solution Explorer you will note two new files: AuPolyline.h and AuPolyline.cpp. These files, inside your DBX module, are responsible to declare and implement your custom entity. Open these two files and walk through the generated code.
Now you need to add a new command, inside your ARX module, to create an instance of your new custom entity. To do that, on the Solution Explorer, right click the AuUserInterface project and click “Set as Startup Project” (project name will turn to bold). Now, click on the first icon of ARXWizard toolbar (a icon with “a>” text). This button will open the “ObjectARX Commands” dialog box. It has two command lists. Right click the first list and select New. A new line will be added to this list with a default command called “MyCommand1”. Click OK.
If you open the acrxEntryPoint.cpp file of AuUserInterface project you will find, inside class CAuUserInterfaceApp, the AuUserInterface_MyCommand1() empty method which will be called when you fire MYCOMMAND1 command inside AutoCAD. Inside this method you will create an instance of your AuPolyline entity.
Remember that your custom entity class implementation is located into the DBX project. Due that you will need to add (through #include compiler instruction) a reference to the AuPolyline class declaration file. To do that, add the following compiler instruction at the beginning of acrxEntryPoint.cpp file of your ARX module right after #include "resource.h" line:

#include "..\AuCustomObjects\AuPolyline.h"
The “..\” part of this include path goes up one folder level (getting back to the Solution’s root folder) and then goes into the DBX project folder. Now you can compile your project and shouldn’t get any errors. Next, open AutoCAD, load first the DBX module and then the ARX module (there will be two text messages at AutoCAD command prompt confirming that the both load processes were succeeded. Type your project’s command “MYCOMMAND1” and run a ZOOM EXTENTS command to see the generated entity. If you also run the LIST command and select this entity you will see its detailed information.
The following code will create a 10 sided polyline (closed) and will add it to the Model Space Block Table Record.

// - AuUserInterface._MyCommand1 command (do not rename)
static void AuUserInterface_MyCommand1(void)
{
// AuPolyline entity
AuPolyline* pL = new AuPolyline();
int nSides = 10;
double incAngle = 2*3.141592 / nSides;
// Add vertex list
for (int i=0; i<nSides; i++)
pL->addVertexAt(i,AcGePoint2d(10*cos(i*incAngle),10*sin(i*incAngle)));
// Set Polyline as closed
pL->setClosed(Adesk::kTrue);
// open the proper entity container
AcDbBlockTable* pBT = NULL;
AcDbDatabase* pDB = acdbHostApplicationServices()->workingDatabase();
pDB->getSymbolTable(pBT,AcDb::kForRead);
AcDbBlockTableRecord* pBTR = NULL;
pBT->getAt(ACDB_MODEL_SPACE, pBTR, AcDb::kForWrite);
pBT->close();
// now, add the entity to container
AcDbObjectId Id;
pBTR->appendAcDbEntity(Id, pL);
pBTR->close();
pL->close();
}

On lines 05-07 we instantiate the entity and initialize some local variables. Next, on lines 9-10 we add a list of vertexes to the entity. Line 12 set the polyline as closed. On lines 14-19 we open the AutoCAD database, open the Block Table container and then get the Model Space container. On lines 21-24 we add our entity do Model Space and then close it and the entity itself.
Note: You cannot delete the entity’s pointer once it is added to AutoCAD database. In this case its management is delegated to AutoCAD and you only need to call the close() method. If you delete this pointer you will cause AutoCAD to crash.

Labels: , ,