Define your AcGiSubEntityTraits class

advertisement
Stylized DWG™ to Autodesk MapGuide®: Spatial
Databases Using ObjectARX® and FDO
Clayton Hotson Autodesk
CP4930
Getting DWG data into your spatial databases can be difficult enough, but preserving the graphical
fidelity at the same time is a real challenge. In this class we will discuss tools and techniques for getting DWG data into
Autodesk MapGuide with Autodesk Feature Data Objects (FDO) while preserving graphical fidelity with AutoCAD®.
Your stylized Autodesk MapGuide maps will still look like their source AutoCAD DWGs. Once your DWG data is
loaded into Autodesk MapGuide, it is available to be viewed and queried online using a powerful set of web mapping
tools. We will cover data migration, data stylization, ObjectARX graphics queries, RealDWG®, and FDO data
>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<
Learning Objectives
At the end of this handout, you will be able to:

Understand the basics of AcGi graphics architecture in AutoCAD

Understand how to leverage this knowledge to obtain low level entity graphics

Understand the basics of AutoCAD database traversal
About the Speaker
Clayton has been a platform and mapping technologies developer for over 20 years. He started out
developing oceanographic survey data collection and analysis applications, and quickly branched out to
include customization of AutoCAD and AutoCAD verticals. Clayton is currently a software architect
working for the EMIA Autodesk Consulting team based in Switzerland.
Insert Class Title as per Title Page
AutoCAD Rendering Basics
AcGi Protocol
In AutoCAD, entities essentially draw themselves. Each entity subscribes to what is called the
AcGi protocol, which allows the system to be interrogated in a uniform manner. AcGi will ask an
entity to draw itself explicitly, passing it the tools to do so. A good analogy is, it is like passing
the brush and canvas to a painter and asking the painter to draw a picture.
Entities participate in this mechanism during regeneration. Entities are regenerated (among
other times) during an AutoCAD REGEN command. The graphics produced during this
operation are cached by AutoCAD so that fast repaints can be made during zooms, pans and
REDRAW commands.
Basic knowledge of this mechanism is crucial for our objective, since understanding how entities
can render themselves will give us the opportunity to ask the entities to yield their graphic
information to us for us to use as we require.
What happens at Regen time?
Figure 1 depicts a regeneration in AutoCAD. AutoCAD traverses all the blocks, and all entities
within each block, asking the entity to draw itself. You can see that the ‘Custom Entity’ is
rendered within ‘Block 2’, which in-turn is rendered through a reference inside Model Space.
Figure 1 – Timeline of entities being rendered during a display regeneration.
2
Insert Class Title as per Title Page
As each entity is regenerated, AcGi will call the relevant overridden functions of the entity to
extract the graphics. When it calls them, it passes specific drawing tools necessary to draw the
entity graphics.
Any object that wishes to be drawn by AcGi must derive from the class AcGiDrawable. Any
object that wishes to be both visible on screen *and* persisted in the AutoCAD DWG as an
entity must derive from the AcDbEntity class. As it happens, AcDbEntity itself is an
AcGiDrawable, therefore the objects we’re most interested in simply derive below AcDbEntity.
Figure 2 depicts AutoCAD performing a regeneration, by 1) creating a WorldDraw object
(AcGiWorldDraw) and passing 3) it into an AcGiDrawable (one of our entities). The
AcGiDrawable entity will use the AcGiWorldDraw object to render itself. Think of
AcGiWorldDraw as the brush/canvas we pass to the painter, our entity.
Note - AcGiViewportDraw is a specialized case of rendering (supporting multiple viewports)
which is not addressed in detail here. More information may be found in the ObjectARX
developer’s guide.
Figure 2 – Host Application (AutoCAD) mechanism for acquiring the graphics in a DWG
3
Insert Class Title as per Title Page
How AutoCAD uses AcGiWorldDraw on AcGiDrawables
Whenever AutoCAD needs to regenerate the graphics to display an entity, the worldDraw() and
viewportDraw() overrides of AcGiDrawable are called in the following manner:
if (!entity->worldDraw(pWd))
for (each relevant viewport)
entity->viewportDraw(pVd);
The worldDraw() function calls subWorldDraw() (the public override of AcGiDrawable) on our
entity to build the entity's graphical representation that can be specified independently of any
particular model-space view or paper-space viewport contexts. (Note – for more information on
subWorldDraw, see the ObjectARX developer’s guide about the Overrule API).
The AcDbEntity::subWorldDraw() function takes a pointer to our canvas/brush object,
AcGiWorldDraw. AcGiWorldDraw is a container class for geometry and traits objects.
Specifically, AcGiWorldDraw contains these two other objects:


AcGiWorldGeometry
AcGiSubEntityTraits
In our painter analogy, AcGiWorldGeometry is used by the painter (our entity) to describe what
shapes to paint and where, and the AcGiSubEntityTraits is used by the painter to describe the
color and textures used.
Representing Geometry with AcGiWorldGeometry
The AcGiWorldGeometry object is used from within the worldDraw() function by using the
AcGiWorldDraw::geometry() function. The AcGiWorldGeometry object writes vectors to
AutoCAD's refresh memory using its set of drawing primitives. A primitive is the lowest-level
instruction used to draw graphical entities. The world geometry object has the following
functions for drawing primitives in world coordinates:









Circle
Circular arc
Polyline
Polygon
Mesh
Shell
Text
Xline
Ray
All basic geometry in AutoCAD is described using these simple primitives.
4
Insert Class Title as per Title Page
Stylizing Entities, AutoCAD Style, with AcGiSubEntityTraits
The AcGiSubEntityTraits object is accessed from witin the worldDraw() function by using the
AcGiWorldDraw::subEntityTraits() function. AcGiSubEntityTraits sets graphical attribute values
using the following set of traits functions:





Color
Layer
Linetype
Polygon fill type
Selection marker
Building Your Own AcGi Vector Taker
Figure 3 depicts the relevant AcGi classes, their class hierarchy and ownership. Our
AcGiWorldDraw (our canvas and brush) derive from the class AcGiCommonDraw, which, in
turn, hold our AcGiGeometry and AcGiSubEntityTraits (more later on AcGiContext).
These objects will be used by each entity to describe how they are drawn. We will define our
versions of these objects to *capture* the graphics as they are rendered by the entity.
Figure 3 – AcGi class diagram
5
Insert Class Title as per Title Page
To utilize these classes, we need only derive from a few of the mentioned classes, implement
the relevant methods, and plug them into the AutoCAD database. The following sections
describe the work required.
A working sample application will be available for interested students.
Define your AcGiWorldGeometry class
The AcGiWorldGeometry class must be derived from in order to collect the geometric primitives
of elements rendered by the AutoCAD graphics subsystem.
This base class includes callbacks for all the basic primitives. Define a derived class and
override each method as required to collect the geometry. Example: Derived class called
AcGiSnoopWorldGeometry, overriding the polyline() member:
Adesk::Boolean AcGiSnoopWorldGeometry::polyline(const Adesk::UInt32 nbPoints,
const AcGePoint3d* pVertexList,
const AcGeVector3d* pNormal,
Adesk::LongPtr lBaseSubEntMarker) const
{
AcGePoint3dArray pts;
for (int i = 0; i < nbPoints; i++)
{
AcGePoint3d ptT = pVertexList[i];
ptT.transformBy(m_ModelMatrix);
pts.append(ptT);
}
… The definition of the geometry is now expressed in the array of points.
You must define a system of retaining the geometry collected from these methods (only enough
for a single entity) so they can be orderly translated during any output process you define. For
the class example, we define simple holder classes and a static array to hold the instances.
Keeping Track of the Correct Space
Notice within the inner loop in the code snippet above that the point obtained from the vertex list
is *transformed* by our member m_ModelMatrix.
Though world coordinates are always used in regeneration, geometry rendered inside of blocks
is rendered relative to the origin of the block it belongs to. Therefore in order to correctly place
geometry we must take into consideration any transform matrix applied during a render.
6
Insert Class Title as per Title Page
To do this, we must override the pushModelTransform() and popModelTransform() methods of
AcGiWorldGeometry and apply the passed transform to a matrix stack. When geometry is
presented to us, we must manually transform the position, scale and rotation values using the
top-level transform, as is done in the example snippet above. Remember to apply this to length
and vector values; anything which represents position, distance or angle in geometry.
Within your AcGiWorldGeometry class, add an AcArray which holds AcGeMatrix3d elements,
and persist one top-level AcGeMatrix3d.
private:
AcArray<AcGeMatrix3d> m_ModelTransformStack;
AcGeMatrix3d m_ModelMatrix;
Use these to retain the correct transformation of geometry by in pushModelTransform() and
popModelTransform() overrides (note – there is more than one overload of these methods).
Adesk::Boolean AcGiSnoopWorldGeometry::pushModelTransform(const AcGeMatrix3d & xMat)
{
// Preserve the previous model matrix...
AcGeMatrix3d mat(m_ModelMatrix);
m_ModelTransformStack.append(mat);
m_ModelMatrix = m_ModelMatrix * xMat;
}
return Adesk::kTrue;
Adesk::Boolean AcGiSnoopWorldGeometry::popModelTransform()
{
if (m_ModelTransformStack.length() == 0)
throw L"Empty Model Transform Stack";
// Pop...
m_ModelMatrix = m_ModelTransformStack.last();
m_ModelTransformStack.removeLast();
return Adesk::kTrue;
}
Define your AcGiSubEntityTraits class
Derive a class from AcGiSubEntityTraits to collect graphical trait information such as color, line
type and line width. Override *at least* the pure virtual methods within this base class.
7
Insert Class Title as per Title Page
For each graphical trait, you will need to decide how best to persist the values passed.
Remember that it is very possible that more than one trait will be assigned for a *single entity*
being regenerated, so you may need to persist a stack of these traits relative to the geometry to
preserve the order. In our class example, however, we retain only one set of trait values for
each entity passed (which will satisfy the large majority of cases).
Implement each member which provides information about the graphical traits. Example –
derived class called AcSnoopSubEntityTraits, overriding setColor(). We set the passed color to
our static color member, latestColor.
public:
static int latestColor;
…
void AcSnoopSubEntityTraits::setColor(const Adesk::UInt16 color)
{
AcCmColor acadColor;
acadColor.setColorIndex(color);
acadColor.setColorMethod(AcCmEntityColor::kByACI);
latestColor = FdoHelper::acadColorToMGColor(acadColor);
}
Define your AcGiContext class
The AcGiContext class provides a common context that can be accessed during all parts of the
regeneration process. It provides information about the current state of the regen. For example,
you can get the current database from the AcGiContext object at any time during the regen
process.
Derive a class from AcGiContext to collect context information about the regeneration. Override
*at least* the pure virtual methods within this base class.
Take a good look at the documentation for AcGiContext to understand what it is you require
from a more global (from a regen point of view) context. For our class example, we only keep
track of the AcDbDatabase pointer passed in the overrides database() and setDatabase().
private:
AcDbDatabase * m_pDb;
…
void setDatabase(AcDbDatabase * pDb)
{
m_pDb = pDb;
8
Insert Class Title as per Title Page
}
Define your AcGiWorldDraw class
Derive a class from AcGiWorldDraw to provide a container for the above classes,
AcGiWorldGeometry, AcGiSubEntityTraits and AcGiContext. You will pass an instance of this
class to the AutoCAD graphics system to perform the regeneration. The graphics system will,
in-turn use *your* tools (the classes above) to describe the relevant geometry and graphical
traits information, at which point we can capture the information. Override *at least* the pure
virtual methods.
Add member pointers for the classes AcGiWorldGeometry, AcGiSubEntityTraits and
AcGiContext, and instantiate all three in the constructor of AcGiWorldDraw. Be sure to delete
them within the destructor. Example – derived class named AcSnoopWorldDraw:
private:
AcGiSnoopWorldGeometry *mpGeom;
AcSnoopSubEntityTraits *mpTraits;
AcSnoopContext
*mpContext;
…
AcSnoopWorldDraw::AcSnoopWorldDraw()
: AcGiWorldDraw()
{
mpGeom = new AcGiSnoopWorldGeometry;
mpTraits = new AcSnoopSubEntityTraits;
mpContext = new AcSnoopContext;
…
AcSnoopWorldDraw::~AcSnoopWorldDraw()
{
if (mpGeom)
delete mpGeom;
if (mpTraits)
delete mpTraits;
if (mpContext)
delete mpContext;
Return the pointers for these three classes in the overrides geometry(), subEntityTraits() and
context() respectively. Example for the override subEntityTraits():
AcGiSubEntityTraits& AcSnoopWorldDraw::subEntityTraits() const
{
9
Insert Class Title as per Title Page
}
return *mpTraits;
Traverse the AutoCAD Database through Model Space and Block References
Now that we have all the AcGi components ready to collect our vectors, we can traverse
through Model Space looking for entities to process. For every entity found we will call
worldDraw(), passing in a single instance of our AcGiWorldDraw class! Using this on most
entities, we can collect every detail the entity provides for rendering itself.
The basic flow is:
1. Instantiate our AcGiWorldDraw derivative (call it ‘snoop’ for this example)
2. Iterate through ModelSpace.
a. Open each entity found in ModelSpace for read and for each one:
i. If the object is a Block Reference
1. Push the block transform using
snoop.geometry().pushTransform()
2. Traverse each entity found in the reference’s block the same way
we traverse ModelSpace (recursion is therefore necessary)
3. Pop the model transform when done.
ii. Otherwise call entity->worldDraw(&snoop)
1. Collect all graphical information about the entity
Exempted Entities
Some entities will not yield any information during worldDraw() using this technique, as they do
not fully subscribe to the AcGi protocol. For these entities, to obtain their graphics, you must
call explode on them and perform the above procedure on each constituent exploded piece
found within the offending outer entity.
For example, the following basic entity types must be exploded: AcDb3dPolyline,
AcDbDimension, AcDbMLeader and AcDbMText. The class example demonstrates how to
handle these cases.
Handling ByLayer and ByBlock Traits
It is common for entities to yield graphical traits to owning block or layer values, by specifying
ByLayer or ByBlock values for traits such as Color, Linetype and LineWeight. Because of this,
we must keep track of these values when we process the entities.
10
Insert Class Title as per Title Page
For each entity, check the layer color, line type and line weight and persist the *top level* value
(i.e. during recursion in blocks you may have multiple calls). Similarly, when recursing into
blocks, we must obtain the color, line type and line weight values for the block and, again, hold
onto only the top-level value. This is demonstrated in the class example code.
Example converting Geometry to FDO
For the sake of brevity, this handout does not demonstrate how to convert collected geometry
and graphical traits to FDO. The supplied code example, however, does demonstrate how this
data is exported to FDO, using the SDF provider.
11
Download