November 2014

Sun Mon Tue Wed Thu Fri Sat
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            










« A quick announcement... | Main | Some background to AutoCAD's MDI implementation and per-document data »

September 25, 2006

Working with specific AutoCAD object types in .NET

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:

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e200d834b5718953ef

Listed below are links to weblogs that reference Working with specific AutoCAD object types in .NET:

blog comments powered by Disqus

Feed/Share

10 Random Posts