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.

No comments :