Using the COM interface for AutoCAD objects from .NET

This question came in a couple of weeks ago from csharpbird, and I promised myself to turn it into a post:

The Hatch class of the current AutoCAD version does not provide the OriginPoint property.How to get/set the OriginPoint of the Hatch object using P/Invoke?

The reason I wanted to delay making an official post is that I have good news (that I couldn't talk about until the product was announced): the Autodesk.AutoCAD.DatabaseServices.Hatch object now has an Origin property in AutoCAD 2008. 🙂

But this does raise an interesting topic - how to get at information that isn't available through the managed interface? P/Invoke is an option for access certain ObjectARX functions, but as stated in this previous post, it cannot be used with instance members, such as AcDbHatch methods.

This particular property (a Hatch's Origin) has been exposed for a few releases through COM; Property Palette support for object properties sometimes drives their COM exposure more quickly than their "managed" exposure (although increasingly the .NET API is there right from the beginning).

Anyway, here's some C# code for AutoCAD 2007 that accesses the COM interface for a hatch object, returning the Origin property. The code first gets the object as a managed hatch, simply to show a technique for going from a managed object to a COM object:

[Update: this is Version 1 of the code, posted before Albert's comment, below. Scroll down for the more elegant Version 2...]

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Interop;

using System.Runtime.InteropServices;

namespace HatchQuery

{

  public class HatchQueryCommands

  {

    [CommandMethod("QH")

    ]

    public void QueryHatchOrigin()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      try

      {

        PromptEntityResult per =

          ed.GetEntity("\nSelect hatch object: ");

        if (per.Status == PromptStatus.OK)

        {

          Transaction tr =

            doc.TransactionManager.StartTransaction();

          using (tr)

          {

            // Open up the object

            DBObject obj =

              tr.GetObject(per.ObjectId, OpenMode.ForRead);

            // Make sure we have a managed hatch object

            Hatch hat = obj as Hatch;

            if (hat != null)

            {

              // Now let's pretend we had our managed hatch,

              // and need to use COM to get at its Origin

              Autodesk.AutoCAD.Interop.AcadApplication oAcad =

                (AcadApplication)Marshal.GetActiveObject(

                  "AutoCAD.Application.17"

                );

              // Let's use the COM API to get a COM reference

              // to the hatch (as a base object)

              object obj2 =

                oAcad.ActiveDocument.ObjectIdToObject(

                  obj.ObjectId.OldId

                );

              // Let's convert that object reference to

              // a COM hatch

              Autodesk.AutoCAD.Interop.Common.AcadHatch oHat =

                obj2 as

                Autodesk.AutoCAD.Interop.Common.AcadHatch;

              if (oHat != null)

              {

                // Now let's get the Origin as an array

                // of doubles, and print the contents

                double[] orig = (double[])oHat.Origin;

                ed.WriteMessage(

                  "\nHere's the hatch origin: X: " +

                  orig[0] + ", Y: " + orig[1]

                );

              }

            }

          }

        }

      }

      catch (System.Exception ex)

      {

        ed.WriteMessage(

          "Exception: " + ex

        );

      }

    }

  }

}

Version 2:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Interop;

using System.Runtime.InteropServices;

namespace HatchQuery

{

  public class HatchQueryCommands

  {

    [CommandMethod("QH")

    ]

    public void QueryHatchOrigin()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

      try

      {

        PromptEntityResult per =

          ed.GetEntity("\nSelect hatch object: ");

        if (per.Status == PromptStatus.OK)

        {

          Transaction tr =

            doc.TransactionManager.StartTransaction();

          using (tr)

          {

            // Open up the object

            DBObject obj =

              tr.GetObject(per.ObjectId, OpenMode.ForRead);

            // Make sure we have a managed hatch object

            Hatch hat = obj as Hatch;

            if (hat != null)

            {

              // a COM hatch

              Autodesk.AutoCAD.Interop.Common.AcadHatch oHat =

                obj.AcadObject as

                Autodesk.AutoCAD.Interop.Common.AcadHatch;

              if (oHat != null)

              {

                // Now let's get the Origin as an array

                // of doubles, and print the contents

                double[] orig = (double[])oHat.Origin;

                ed.WriteMessage(

                  "\nHere's the hatch origin: X: " +

                  orig[0] + ", Y: " + orig[1]

                );

              }

            }

          }

        }

      }

      catch (System.Exception ex)

      {

        ed.WriteMessage(

          "Exception: " + ex

        );

      }

    }

  }

}

And here's what happens when you run the code and select a hatch with an origin of 50,30:

Command: QH

Select hatch object:

Here's the hatch origin: X: 50, Y: 30

  1. This code could be simplified by using the DBObject.AcadObject property. This propery provides the "COM object" for a ".net object". There's also a static FromAcadObject method to go the other way around.

    Albert

  2. Kean, Albert ;

    Thank you.

  3. using Autodesk.AutoCAD.EditorInput;
    when using this for some reason the vs05 does nor give me the EditorInput as a choice could you pleas let me know what i'm doing wrong. sorry this is a elementry question but i'm very new to this. thank you for your patience.

  4. My first instinct would be to check the assembly references, to make sure they’re to the correct version. You haven’t mentioned which release of AutoCAD you’re working with, but this shouldn’t matter – I believe this namespace existed from when we introduced the .NET layer in AutoCAD.

    I’d suggest trying removing and re-adding the assembly references to acmgd.dll and acdbmgd.dll, to see if that helps.

  5. Hi Kean

    Great blog, thanks. Could you also show how to get from AcadObject to a Autocad Mechanical in my case a McadSymbol object?

    Cheers
    Tore

  6. Sorry

    My question was wrong. Asumed the McadSymbol was the equivalent of the AMDTNOTE object in ACAD M.

    If you know what to object to cast too in .NET using COM, feel free to answer.

    Regards
    Tore

  7. Adam Nagy, a member of the DevTech team in Prague, told me of a DevNote he'd published to the ADN site on this topic:

    >>>
    At this time there is no API support for AmdtNote objects. One possible workaround is using the AMEDIT command. Here are a couple of examples showing this approach.

    1) This C# example uses the AutoCAD COM API:

    private void button1_Click(object sender, System.EventArgs e)
    {
    AcadApplication app = (AcadApplication)System.Runtime.InteropServices.Marshal.GetActiveObject("AutoCAD.Application");

    AcadEntity ent;
    double []pt = new double [2];

    AcadDocument drawing = app.ActiveDocument;
    Object out1, out2;
    drawing.Utility.GetEntity(out out1, out out2, "Select a AMDTNOTE :");
    ent = (AcadEntity)out1;
    pt = (Double[])out2;

    drawing.SetVariable("CMDDIA", 0);
    drawing.SendCommand("AMEDIT (handent \"" + ent.Handle + "\") " + "\n");

    string str = (String)drawing.GetVariable("LASTPROMPT");

    drawing.SendCommand("");
    drawing.SetVariable("CMDDIA", 1);

    int start = str.IndexOf("<");
    System.Windows.Forms.MessageBox.Show(str.Substring(start + 2, (str.Length - 5) - start));
    }

    2) This example uses the AutoCAD .NET API. (acedCmd is enabled through PInvoke)

    [DllImport("acad.exe", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedCmd")]
    private static extern int acedCmd(System.IntPtr vlist);

    [CommandMethod("AsdkCmd1")]
    static public void test()
    {
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Editor ed = doc.Editor;

    ObjectId entId = ed.GetEntity("Select AmdtNote...").ObjectId;

    Application.SetSystemVariable("CMDDIA", 0);

    ResultBuffer rb = new ResultBuffer();
    rb = new ResultBuffer();
    rb.Add(new TypedValue(5005, "AMEDIT"));
    rb.Add(new TypedValue(5006, entId));
    rb.Add(new TypedValue(5005, "")); //"\n"));
    acedCmd(rb.UnmanagedObject);

    string str = (String)Application.GetSystemVariable("LASTPROMPT");

    Application.SetSystemVariable("CMDDIA", 1);

    int start = str.IndexOf("<");
    string strText = str.Substring(start + 2, (str.Length - 5) - start);
    }

    <<<

  8. vinay t tharakan Avatar
    vinay t tharakan

    sir,

    am new to this objectarx programming through c#.
    i tried the above code for hatch, but i hve problem relating to
    "using Autodesk.AutoCAD.Interop;" where am not getting the ".interop"
    please help me with the above.....

    regards
    vinay t tharakan

  9. Kean Walmsley Avatar

    Vinay -

    You will need to add a project references to AutoCAD's Type Libraries.

    Project -> Add Reference... -> COM -> Select "AutoCAD 200x Type Library" and "AutoCAD/ObjectDBX Common 17.0 Type Library".

    Regards,

    Kean

  10. vinay t tharakan Avatar
    vinay t tharakan

    thanks for the help sir......

  11. I'm converting handles into object ids as shown above using Autocad 2007. It works fine for an object, unless that object is a copy of another object. It then throws a null exception, even though the object is there. How can I get around this?

    Thanks,
    Derek

  12. Derek -

    It's not clear what you mean by a copy. And which code do you mean - code in the post itself (which doesn't deal with handles) or in one of the comments?

    I suggest posting some code to via ADN support, if you're a member, or otherwise someone on the AutoCAD .NET Discussion Group may be able to help.

    Kean

  13. Kean,

    I am trying to delete existing tables in a drawing using ObjectDBX. Below is the code snippet. I am able to delete one few tables in the drawings but remaining are not deleted. Am I missing out something here?, please help me out. Thanks for the time.

    Dim dbxDoc As AxDbDocument

    Set dbxDoc = ThisDrawing.Application.GetInterfaceObject "ObjectDBX.AxDbDocument.17")

    dbxDoc.Open (StrNextFile)

    dobj = dbxDoc.ModelSpace.count

    If dobj > 2 Then
    For I = 0 To dobj
    If ((dbxDoc.ModelSpace.Item(I).ObjectName = "AcDbTable") And (dbxDoc.ModelSpace.Item(I).Layer = "MISC")) Then
    dbxDoc.ModelSpace.Item(I).Delete
    End If
    Next
    End If

    Thanks
    Shankar J

  14. Kean Walmsley Avatar

    Shankar,

    This isn't a forum for support.

    Your comment doesn't appear to relate directly to this post, so please submit your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Regards,

    Kean

  15. Dear Mr. Walmsley

    How to set Hatch object orgin point to the left bottom point of itself in AutoCAD 2007 by .Net?

  16. Dear Qu,

    As this posts says, prior to AutoCAD 2008 the origin property was only exposed via COM. So you can use that to set it in AutoCAD 2007.

    If this doesn't help, I suggest posting your question to the ADN team, if you're a member, or otherwise the AutoCAD .NET Discussion Group.

    Kean

Leave a Reply to vinay t tharakan Cancel reply

Your email address will not be published. Required fields are marked *