Most of the functions to access objects in the AutoCAD drawing return generic objects/entities. The base type for objects stored in the AutoCAD database is “DBObject” – which can be used to access common persistence information such as the handle, etc. – but there is no such thing as a pure DBObject: all DBObjects actually belong to a type that is derived from this base (whether that’s a “Line”, “Circle”, “BlockReference”, etc.).
The next level up in the hierarchy is “Entity”, from where you can access graphical properties such as color, layer, linetype, material, etc. The sample code in this previous entry shows how to treat a DBObject as an Entity – in this entry we’re going to look at how to access information from an even more specific type, such as the center point and radius of a Circle.
In order to find out what kind of object you’re dealing with, you need to use some kind of runtime type system. A compile-time type system is not going to be enough, as in many cases you simply don’t know what type of object it is you’re going to find in a particular location in the drawing database – especially when asking the user to select entities or when reading them from one of the block table records such as the model-space.
C++ introduced a system for RTTI (RunTime Type Identification) after ObjectARX was first implemented, so AutoCAD maintains its own class hierarchy in memory. In order to use a more specific class in ObjectARX than the generic AcDbObject, you typically use isKindOf() or cast(), which ultimately use the AcDb class hierarchy behind the scenes to determine whether the pointer conversion operation is safe. The C++ standard now includes dynamic_cast<> to perform the equivalent task, but this is not enabled for standard ObjectARX types. As far as I recall, it is enabled for some of our other APIs (such as the Object Modeling Framework, the C++ API in Architectural Desktop that sits on top of ObjectARX), but for ObjectARX the existing type system has proven adequate until now.
Here’s some ObjectARX code showing this:
// objId is the object ID of the entity we want to access
Acad::ErrorStatus es;
AcDbEntity *pEnt = NULL;
es = acdbOpenAcDbEntity( pEnt, objId, AcDb::kForRead );
if ( es == Acad::eOk )
{
AcDbCircle *pCircle = NULL;
pCircle = AcDbCircle::cast( pEnt );
if ( pCircle )
{
// Access circle-specific properties/methods here
AcGePoint3d cen = pCircle->center();
// ...
}
pEnt->close();
}
In a managed environment you get access to the .NET type system. Here’s an example of what you might do in VB.NET:
[Note: the following two fragments have been pointed out in comments as being sub-optimal - please see further down for a better technique...]
' tr is the running transaction
' objId is the object ID of the entity we want to access
Dim obj As DBObject = tr.GetObject(objId, OpenMode.ForRead)
Try
Dim circ As Circle = CType(obj, Circle)
' Access circle-specific properties/methods here
' ...
Catch ex As InvalidCastException
' That's fine - it's just not a circle...
End Try
obj.Dispose()
And in C#:
// tr is the running transaction
// objId is the object ID of the entity we want to access
DBObject obj = tr.GetObject(objId, OpenMode.ForRead);
try
{
Circle circ = (circle)obj;
// Access circle-specific properties/methods here
// ...
}
catch (InvalidCastException ex)
{
// That's fine - it's just not a circle...
}
obj.Dispose();
[Here is the more elegant way to code this...]
VB.NET:
' tr is the running transaction
' objId is the object ID of the entity we want to access
Dim obj As DBObject = tr.GetObject(objId, OpenMode.ForRead)
If TypeOf (obj) Is Circle Then
Dim circ As Circle = CType(obj, Circle)
' Access circle-specific properties/methods here
' ...
End If
obj.Dispose()
C#:
// tr is the running transaction
// objId is the object ID of the entity we want to access
DBObject obj = tr.GetObject(objId, OpenMode.ForRead);
Circle circ = obj as Circle;
if (circ != null)
{
// Access circle-specific properties/methods here
// ...
}
obj.Dispose();
So now let's plug that technique into the previous sample. All we're going to do is check whether each entity that was selected is a Circle, and if so, print out its radius and center point in addition to the common entity-level properties we listed in last time.
Here's the VB.NET version:
Imports Autodesk.AutoCAD
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput
Namespace SelectionTest
Public Class PickfirstTestCmds
' Must have UsePickSet specified
<CommandMethod("PFT", _
(CommandFlags.UsePickSet _
Or CommandFlags.Redraw _
Or CommandFlags.Modal))> _
Public Shared Sub PickFirstTest()
Dim doc As Document = _
Application.DocumentManager.MdiActiveDocument
Dim ed As Editor = doc.Editor
Try
Dim selectionRes As PromptSelectionResult
selectionRes = ed.SelectImplied
' If there's no pickfirst set available...
If (selectionRes.Status = PromptStatus.Error) Then
' ... ask the user to select entities
Dim selectionOpts As PromptSelectionOptions
selectionOpts = New PromptSelectionOptions
selectionOpts.MessageForAdding = _
vbLf & "Select objects to list: "
selectionRes = ed.GetSelection(selectionOpts)
Else
' If there was a pickfirst set, clear it
ed.SetImpliedSelection(Nothing)
End If
' If the user has not cancelled...
If (selectionRes.Status = PromptStatus.OK) Then
' ... take the selected objects one by one
Dim tr As Transaction = _
doc.TransactionManager.StartTransaction
Try
Dim objIds() As ObjectId = _
selectionRes.Value.GetObjectIds
For Each objId As ObjectId In objIds
Dim obj As Object = _
tr.GetObject(objId, OpenMode.ForRead)
Dim ent As Entity = _
CType(obj, Entity)
' This time access the properties directly
ed.WriteMessage(vbLf + "Type: " + _
ent.GetType().ToString)
ed.WriteMessage(vbLf + " Handle: " + _
ent.Handle().ToString)
ed.WriteMessage(vbLf + " Layer: " + _
ent.Layer().ToString)
ed.WriteMessage(vbLf + " Linetype: " + _
ent.Linetype().ToString)
ed.WriteMessage(vbLf + " Lineweight: " + _
ent.LineWeight().ToString)
ed.WriteMessage(vbLf + " ColorIndex: " + _
ent.ColorIndex().ToString)
ed.WriteMessage(vbLf + " Color: " + _
ent.Color().ToString)
' Let's do a bit more for circles...
If TypeOf (obj) Is Circle Then
' Let's do a bit more for circles...
Dim circ As Circle = CType(obj, Circle)
ed.WriteMessage(vbLf + " Center: " + _
circ.Center.ToString)
ed.WriteMessage(vbLf + " Radius: " + _
circ.Radius.ToString)
End If
obj.Dispose()
Next
' Although no changes were made, use Commit()
' as this is much quicker than rolling back
tr.Commit()
Catch ex As Autodesk.AutoCAD.Runtime.Exception
ed.WriteMessage(ex.Message)
tr.Abort()
End Try
End If
Catch ex As Autodesk.AutoCAD.Runtime.Exception
ed.WriteMessage(ex.Message)
End Try
End Sub
End Class
End Namespace
And here it is in C#:
using Autodesk.AutoCAD;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
namespace SelectionTest
{
public class PickfirstTestCmds
{
// Must have UsePickSet specified
[CommandMethod("PFT", CommandFlags.UsePickSet |
CommandFlags.Redraw |
CommandFlags.Modal)
]
static public void PickFirstTest()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
try
{
PromptSelectionResult selectionRes =
ed.SelectImplied();
// If there's no pickfirst set available...
if (selectionRes.Status == PromptStatus.Error)
{
// ... ask the user to select entities
PromptSelectionOptions selectionOpts =
new PromptSelectionOptions();
selectionOpts.MessageForAdding =
"\nSelect objects to list: ";
selectionRes =
ed.GetSelection(selectionOpts);
}
else
{
// If there was a pickfirst set, clear it
ed.SetImpliedSelection(new ObjectId[0]);
}
// If the user has not cancelled...
if (selectionRes.Status == PromptStatus.OK)
{
// ... take the selected objects one by one
Transaction tr =
doc.TransactionManager.StartTransaction();
try
{
ObjectId[] objIds =
selectionRes.Value.GetObjectIds();
foreach (ObjectId objId in objIds)
{
DBObject obj =
tr.GetObject(objId, OpenMode.ForRead);
Entity ent = (Entity)obj;
// This time access the properties directly
ed.WriteMessage("\nType: " +
ent.GetType().ToString());
ed.WriteMessage("\n Handle: " +
ent.Handle.ToString());
ed.WriteMessage("\n Layer: " +
ent.Layer.ToString());
ed.WriteMessage("\n Linetype: " +
ent.Linetype.ToString());
ed.WriteMessage("\n Lineweight: " +
ent.LineWeight.ToString());
ed.WriteMessage("\n ColorIndex: " +
ent.ColorIndex.ToString());
ed.WriteMessage("\n Color: " +
ent.Color.ToString());
// Let's do a bit more for circles...
Circle circ = obj as Circle;
if (circ != null)
{
ed.WriteMessage("\n Center: " +
circ.Center.ToString());
ed.WriteMessage("\n Radius: " +
circ.Radius.ToString());
}
obj.Dispose();
}
// Although no changes were made, use Commit()
// as this is much quicker than rolling back
tr.Commit();
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage(ex.Message);
tr.Abort();
}
}
}
catch(Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage(ex.Message);
}
}
}
}
You'll notice the copious use of ToString - this just saves us having to get (and print out) the individual values making up the center point co-ordinate, for instance.
Let's see this running with entities selected from one of AutoCAD's sample drawings. Notice the additional data displayed for the circle object:
Command: PFT
Select objects to list: 1 found
Select objects to list: 1 found, 2 total
Select objects to list: 1 found, 3 total
Select objects to list: 1 found, 4 total
Select objects to list:
Type: Autodesk.AutoCAD.DatabaseServices.Circle
Handle: 1AB
Layer: Visible Edges
Linetype: Continuous
Lineweight: LineWeight035
ColorIndex: 179
Color: 38,38,89
Center: (82.1742895599028,226.146274397998,0)
Radius: 26
Type: Autodesk.AutoCAD.DatabaseServices.Line
Handle: 205
Layer: Visible Edges
Linetype: Continuous
Lineweight: LineWeight035
ColorIndex: 179
Color: 38,38,89
Type: Autodesk.AutoCAD.DatabaseServices.BlockReference
Handle: 531
Layer: Dimensions
Linetype: ByLayer
Lineweight: ByLayer
ColorIndex: 256
Color: BYLAYER
Type: Autodesk.AutoCAD.DatabaseServices.Hatch
Handle: 26B
Layer: Hatch
Linetype: Continuous
Lineweight: LineWeight009
ColorIndex: 179
Color: 30,30,71
Command: