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!

24 comments :

Fernando Malard said...

Hi Nimish,

AcDb3dSolid is a heavy class which uses several features to define itself. Due that it can be very slow in some situations you derive from it. If you pay attention to some aspects your custom entity can be as fast as AcDb3dSolid class is. It is really up to you how to setup its behavior.

You need to analyze if its better to create a custom entity derived from AcDb3dSolid or to place the solid as an embedded member of your custom class. Both approaches are better for specific situations.

Regards,
Fernando.

Fernando Malard said...

Hi Nimish,

Unfortunately I don't have such a sample code.

Basically I recommend you to put a AcDb3dSolid* as a class member, call its new operator at your custom entity´s class constructor and destruct it at its destructor.

Create a "solid build" function to be called properly and inside your member funtions foward calls to this pointer.

Regards,
Fernando.

Anonymous said...

Hola Fernando;

Hope you still around...

I have a question for you, I am doing my first custom object, and yes I know my background is not the best about C++, specific with Classes... anyway this is in general what I have:

class AcDbStall : public AcDbEntity {

public:
ACRX_DECLARE_MEMBERS( AcDbStall );

private:

AcGePoint3d sp; // start point for the Stall Line
AcGePoint3d ep; // end point for the Stall Line
double wid; // stall width
double len; // stall length

public:

AcDbStall::AcDbStall()
{
sp.set(0,0,0);
ep.set(1,1,1);
}

AcDbStall::~AcDbStall() {}

// methods
public:
Acad::ErrorStatus setStart( double* );
Acad::ErrorStatus getStart( double* );
Acad::ErrorStatus setEnd( double* );
Acad::ErrorStatus getEnd( double* );
Acad::ErrorStatus setWidth( double );
Acad::ErrorStatus getWidth( double* );
Acad::ErrorStatus setLength( double );
Acad::ErrorStatus getLength( double* );

// overridden methods from AcDbEntity
public:
virtual Adesk::Boolean worldDraw( AcGiWorldDraw* mode );
virtual Acad::ErrorStatus transformBy( const AcGeMatrix3d& );
virtual Acad::ErrorStatus getGripPoints( AcGePoint3dArray&, AcDbIntArray&, AcDbIntArray& ) const;
virtual Acad::ErrorStatus moveGripPointsAt( const AcDbIntArray&, const AcGeVector3d& );

// overridden methods from AcDbObject
public:
virtual Acad::ErrorStatus dwgInFields( AcDbDwgFiler* );
virtual Acad::ErrorStatus dwgOutFields( AcDbDwgFiler* ) const;
virtual Acad::ErrorStatus dxfInFields( AcDbDxfFiler* );
virtual Acad::ErrorStatus dxfOutFields( AcDbDxfFiler* ) const;

};

It is going to be for parking layout program, I have my code running fine, just for the double parking stall layout... but now I want to implement the ability to layout the stalls over arcs, so how can I do that?

Do I need to create another class for that in particular and override again in there all the AcDbEntity and AcDbObject methods too?

And here is just a simple function to be use from a command call:

void drawSStall() {
AcDbStall *pLine=new AcDbStall();
ads_point sp, ep;

if (acedGetPoint(NULL, "\nSpecify first point: ", sp) != RTNORM) {
acdbFail("\nInvalid point. ");
}
if (acedGetPoint(sp, "\nSpecify next point: ", ep) != RTNORM) {
acdbFail("\nInvalid point. ");
}

pLine->setStart(sp);
pLine->setEnd(ep);

pLine->setWidth(108.0);
pLine->setLength(240.0);

AcDbObjectId curSpaceId=CURDB()->currentSpaceId();
AcDbBlockTableRecord *pBlkRec=NULL;
if (acdbOpenObject(pBlkRec, curSpaceId, AcDb::kForWrite)==Acad::eOk) {
pBlkRec->appendAcDbEntity(pLine);
pBlkRec->close();
pLine->close();
}
}

I am stuck in this part:

AcDbStall *pLine=new AcDbStall();

How, or what I need to do, in order to come up with different types of parking layout stalls?

Hope that the above could make some sense and heard from you...

Thank you,
Luis

Fernando Malard said...

Hi Luis,

The first thing you have to do is to analyze if it worth to create a new entity due the Arc capabilities you would like to use.

Most of times this is not the best option and then you can only add a parameter to your class defining which type of Stall it will be.

If the behavior of this Arc Stall is completely different from the regular Stall you'll really need to create a second class.

Note that the ArcStall can derive from regular Stall and inherit all its functionalities (this is C++).

The you can redefine your methods and add additional parameters for the ArcStall.

When creating the custom entity your user will choose between these two types of entities.

Hope this help.
Regards,
Fernando.

Anonymous said...

Thank you Fernando;

I am implementing first with some new parameters and an optional method... appears to work.

If after some tests, something came up wrong, I will move to the second choice and make another class just for the ArcStall.

Thank you so much!
Luis.

Anonymous said...

Hi Guys,

Can anyone tell my why I am not allowed to derive a class from a AcDb3dPolyline, or AcDb2dPolyline. When my application initialize, I get an AutoCAD Fatal error message stating that this is not allowed. Waht am I doing wrong?

Fernando Malard said...

Hello Dawie,

This is a known limitation. Accordingly to ObjectARX manual:

Applications should not derive classes from the following:

AcDbDimension
AcDbSymbolTable, AcDbSymbolTableRecord, and all classes derived from them
AcDbBlockBegin
AcDbBlockEnd
AcDbSequenceEnd
AcDb2dPolyline
AcDb2dVertex
AcDb3dPolyline
AcDb3dPolylineVertex
AcDbPolygonMesh
AcDbPolygonMeshVertex
AcDbPolyFaceMesh
AcDbPolyFaceMeshVertex
AcDbFaceRecord
AcDbViewport
AcDbMInsertBlock
AcDbVertex

Anonymous said...

Thanks Fernando, I thought so.

Thanks for a very helpfull blog! Do you perhaps have an example on how to control the contents of the properties bar from a custom entity?

Fernando Malard said...

Hi Dawie,

I don't have this type of sample but there are a couple of samples that show how to use static/dynamic properties:

ObjectARX 2007\samples\editor\simpledynprops

ObjectARX 2007\samples\editor\Palettes\boltWrapper

I think these two samples will help you.

Best Regards,

Anonymous said...

Hello Fernando!
Thank you for your work and efforts you put here: the best internet ObjectArx source info!
I have a question for you:
I like to create a custom entity which contains a lot of line segments. The lines itself is not important for me, but when I double click my entity, all segments have to be selected as a single entity and to return some entity common specific data, like a few String data. I do not want to use block with attached data, but pure custom entities.
How could I define the member variables of this custom entity, and how could be inserted as part of it?
Right now I'm not interested in details for every line segments, maybe in the future as an advance step.
John

Fernando Malard said...

Hello John,

I would recommend you to derive from AcDbPolyline.

I will post a new article with the link of my AU2008 class PDF which shows an example of this type of implementation.

Regards.

Anonymous said...

Hello Fernando!
Thank you for your fast replay!
I do not want to link with any specific class (AcDbPolyline as you suggest), but just to be pure custom entity as I said.
Any idea on how to start will be appreciate!
John

Fernando Malard said...

John,

The most basic class to derive from is AcDbEntity. In this case you will need to implement all the features by yourself.

I think this is a lot of work.
Have you consider to use AcDbXRecord to store your custom data?

It may do the trick...

Further, if you are using AutoCAD 2010 there is a new possibility to customize native entities behavior called Entity Overrule. Take a look here:

http://through-the-interface.typepad.com/through_the_interface/2009/04/customizing-the-display-of-standard-autocad-objects-using-net.html

Regards,

Rajendra said...

Hi Fernando,
I did not get idea on inheritance with AcDbObject.
I wish to inheritance the AcDbPoint having member variable as line AcDbObjectID.

I have looked over custobj example. I found it has member variable as integer and is kept in result buffer. Is it possible to store keep AcDbObjectID in resbuff??
Hope to get ideas regarding my issue as soon as possible. Thank you.

Fernando Malard said...

Rajendra,

I think I didn't understand the logic behind what you are trying to do.

Could you better explain what you need your code to do so I could point you to the right direction?

Regards.

Rajendra said...

Hi Fernando,
Sorry to say that I have lots of queries and hope to get fulfilled from you.
1) I have more than two different types of data sets to store in XData. Can I save them using different application Names?

2) How can I fill the circle made by AcDbCircle?

3) When I tried to make customize AcDbPoint class, I am not able to display the point. Is there others things to consider while displaying?
I did reference of your customize example having string and int as member variables.
I wish to customize AcDbCirlce and keep the member variables stored there.
This much for now..:)
Thank you.

Anonymous said...

Hi Fernando,
I could not fill the circle. Can i get the snippet code for filling the circle made by AcDbCircle.
I dont know I could not see the AcGi* manuals...:(
Thank you.

Fernando Malard said...

Rajendra,

1) Yes, absolutely. I just recommend you use a prefix with your company name, something like:

YOURCOMPANYDATASET
YOURCOMPANYEXTRADATA
etc.

This would avoid any clash with other possible applications that use the XData storage.

2)To fill a circle entity you may use the AcDbHatch entity with a SOLID style and with your circle as the hatch loop. Take a look at ARX docs about AcDbHatch.

3)If you override an existing entity you can either override or not their methods. If you override the drawing method (worldDraw) you can first call your base class method to do its things and then add your own graphics. The other options it to totally ignore the base class drawing and don't call it.

Regards,

Fernando Malard said...

Hello,

If you are using AcGi primitives to draw your entity you may force the fill type to simulate a filled geometry. Something like this:


// Save current fill type
AcGiFillType oldFillType = pDraw->subEntityTraits().fillType();
// Force to fill always so any closed loop will be painted
pDraw->subEntityTraits().setFillType(kAcGiFillAlways);
// Draw something
pDraw->rawGeometry()->polygon(5, pts);
// Restore old fill type
pDraw->subEntityTraits().setFillType(oldFillType);


Regards.

Anonymous said...

Our software is based on AutoCAD 2013.
It allow user to create custom objects which are made up of small elements like box, cylinder, circle, line etc.

User can specify different layer for each of such sub element in the custom object.

Software managed to assign color as per layer of each sub component in same way as described here (by calling -: subEntityTraits().SetColor).
i.e. Custom object is made up of box & cylinder. If box is on layer “A”, then it will have color of “A”. If cylinder is on layer “B” then it will have color of “B”.
Here on / off state of layer "A" / "B" affects correctly visibility of sub entities (box / cylinder) on respective layer.

Is there any other way to apply layer transparency to sub elements of custom object (similar to color).
As per ObjectARX 2013 - "AcGiSubEntityTraits” has method “setTransparency” but it is documented that this method is “For future use and currently has no effect.”

Any help "to assign layer transparency to sub entities (on that layer) of custom object" is highly appreciated.

Thanks a lot!

with regards
ND.

Fernando Malard said...

Hi ND,

I don't think there is an alternative for that.
You will need to wait Autodesk to turn on this method.

Regards,

Anonymous said...

Hi Fernando,

Thanks a lot for your reply.

with regards
ND.

Anonymous said...

Hello Fernando,

Can you give me directions to find solution for my task?

How can i use dialog window to edit some properties I have been added to my custom entity class?

Is there similar primer project in objectArx samples?

Marko

Fernando Malard said...

Yes, there is a way to do it.

Check this out:
http://through-the-interface.typepad.com/through_the_interface/2008/11/adding-custom-p.html

Regards,