Dynamic .NET in AutoCAD 2013

Another really interesting, developer-oriented feature in AutoCAD 2013 is something we've been calling "Dynamic .NET". I don't know whether that's official branding, or not – I suspect not – but it's the moniker we've been using to describe this capability internally and to ADN members.

The capability is based on an addition to .NET in version 4.0: to complement (or perhaps just as part of) the integration of the Dynamic Language Runtime into the core .NET Framework, various interfaces – including IDynamicMetaObjectProvider – were provided to developers to let their objects participate in "dynamic" operations.

Providers of APIs therefore now have the option to make certain classes dynamic – for instance, having them implement the IDynamicMetaObjectProvider interface – which means they can effectively be scripted, without binding properties and methods at compile-time. This implements a concept known as duck typing, and is at the core of many scripting language implementations. You may also hear it referred to as late binding: take a look at this excellent post by Eric Lippert if you'd like to get a better understanding of what that term means.

So what have we actually done with AutoCAD 2013? We have made Autodesk.AutoCAD.DatabaseServices.ObjectId support dynamic operations: you can declare an ObjectId using the dynamic keyword in C#, and you can then choose to access any of the properties or methods exposed by the object possessing that ID directly from the ObjectId itself. The ObjectId then takes care of the open & close operations implicitly, so there's no need to mess around with transactions, etc.

If you're interested in more background to this, I suggest taking a look at Albert Szilvasy's class from AU 2009, where he gave us a hint of what was to come (although it was far from being a certainty, at that time – we have since found it to be a popular option via our annual API wishlist surveys).

OK, so what does this mean, in practice? Here's some statically-typed C# code that iterates through the layer table and prefixes all layers (other than layer "0") with a string:

public static void ChangeLayerNames()

{

  Database db = HostApplicationServices.WorkingDatabase;

 

  using (Transaction tr = db.TransactionManager.StartTransaction())

  {

    LayerTable lt =

      (LayerTable)tr.GetObject(

        db.LayerTableId, OpenMode.ForRead

      );

 

    foreach (ObjectId ltrId in lt)

    {

      LayerTableRecord ltr =

        (LayerTableRecord)tr.GetObject(ltrId, OpenMode.ForRead);

 

      if (ltr.Name != "0")

      {

        ltr.UpgradeOpen();

        ltr.Name = "First Floor " + ltr.Name;

      }

    }

    tr.Commit();

  }

}

Here's how the code could look if using dynamic types. I've done my best not to exaggerate the differences in numbers of lines by using different conventions in the two implementations, but I did have to add more line breaks in the above code to have it fit the width of this blog.

public static void ChangeLayerNamesDynamically()

{

  dynamic layers =

    HostApplicationServices.WorkingDatabase.LayerTableId;

 

  foreach (dynamic l in layers)

    if (l.Name != "0")

      l.Name = "First Floor " + l.Name;

}

Something that needs to be stressed: as the properties and methods are effectively being determined and then accessed/called at run-time, there is no IntelliSense available when coding dynamically (something Python and Ruby developers are used to, but we statically-typed .NET developers are probably less so). It would not be impossible to implement, but it would be very difficult: aside from types being inferred at the time code is being written (I'll use the term develop-time, although I haven't seen it used before), there would need to be some work done to effectively populate the list of properties & methods, which would typically mean opening the object and reflecting over its protocol… not at all straightforward to implement, as far as I can tell, especially when you don't actually have AutoCAD running to help with accessing the objects.

A lack of IntelliSense does mean this style of coding it likely to be less accessible to novice coders, unless a more integrated REPL environment becomes available (as this is something that tends to balance out the lack of other develop-time tooling when scripting – just as people have found with the ability to execute fragments of AutoLISP at AutoCAD's command-line, over the years).

I can see a couple of ways a REPL might end up being implemented. One would be to host the DLR and (say) the IronPython scripting engine (just as Albert showed during his above-mentioned AU class, as I did at my own AU 2009 class, and we've seen previously on this blog). Another would be to wait for the fruits of the future .NET Framework feature codenamed Roslyn, which enables Compiler as a Service for .NET  code, as this seems it would be a likely way to execute C# and VB.NET in a more dynamic way.

But that's all for the future, and is very much my own speculation, at this stage.

One question that has come up in relation to "Dynamic .NET" in AutoCAD is around performance: as we're effectively opening and closing an object every time we execute a method (and we may actually open and close an object twice – once for read, once for write – when we read one of its properties, modify it and store it back), isn't there a performance impact? I admit I haven't performed extensive benchmarking of this, and there is likely to be some overhead if dealing with lots of properties and methods. But in terms of getting something working quickly – which you can then tune for performance later, as needed – this is likely to be a valuable tool for developers, irrespective of the performance impact (which I'd hope to be modest).

And it really comes into its own when used in combination with LINQ. Here's some code that Stephen Preston put together to test out some of the possibilities with LINQ (I've included the CLN and CLN2 commands – shown above – for completeness).

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.EditorInput;

using System.Collections.Generic;

using System.Linq;

 

namespace DynamicTyping

{

  public class Commands

  {

    [CommandMethod("CLN")]

    public static void ChangeLayerNames()

    {

      Database db = HostApplicationServices.WorkingDatabase;

 

      using (

        Transaction tr = db.TransactionManager.StartTransaction()

      )

      {

        LayerTable lt =

          (LayerTable)tr.GetObject(

            db.LayerTableId, OpenMode.ForRead

          );

 

        foreach (ObjectId ltrId in lt)

        {

          LayerTableRecord ltr =

            (LayerTableRecord)tr.GetObject(ltrId, OpenMode.ForRead);

 

          if (ltr.Name != "0")

          {

            ltr.UpgradeOpen();

            ltr.Name = "First Floor " + ltr.Name;

          }

        }

 

        tr.Commit();

      }

    }

 

    [CommandMethod("CLN2")]

    public static void ChangeLayerNamesDynamically()

    {

      dynamic layers =

        HostApplicationServices.WorkingDatabase.LayerTableId;

 

      foreach (dynamic l in layers)

        if (l.Name != "0")

          l.Name = "First Floor " + l.Name;

    }

 

    // Adds a custom dictionary in the extension dictionary of

    // selected objects. Uses dynamic capabilities, but not LINQ.

 

    [CommandMethod("AddMyDict")]

    public void AddMyDict()

    {

      Document doc = Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      PromptSelectionResult res = ed.GetSelection();

 

      if (res.Status != PromptStatus.OK)

        return;

 

      foreach (dynamic ent in res.Value.GetObjectIds())

      {

        dynamic dictId = ent.ExtensionDictionary;

        if (dictId == ObjectId.Null)

        {

          ent.CreateExtensionDictionary();

          dictId = ent.ExtensionDictionary();

        }

        if (!dictId.Contains("MyDict"))

          dictId.SetAt("MyDict", new DBDictionary());

      }

    }

 

    // Adds a custom dictionary in the extension dictionary of

    // selected objects, but this time using dynamic capabilities

    // with LINQ. Conceptually simpler, but with some performance

    // overhead, as we're using two queries: one to get entities

    // without extension dictionaries (and then add them) and the

    // other to get entities with extension dictionaries.

 

    [CommandMethod("AddMyDict2")]

    public void AddMyDict2()

    {

      PromptSelectionResult res =

        Application.DocumentManager.MdiActiveDocument.Editor.

          GetSelection();

 

      if (res.Status != PromptStatus.OK)

        return;

 

      // Query for ents in selset without ExtensionDictionaries

 

      var noExtDicts =

        from ent in res.Value.GetObjectIds().Cast<dynamic>()

          where ent.ExtensionDictionary == ObjectId.Null

          select ent;

 

      // Add extension dictionaries

 

      foreach (dynamic ent in noExtDicts)

        ent.CreateExtensionDictionary();

 

      // Now we've added the ext dicts, we add our dict to each

 

      var noMyDicts =

        from ent in res.Value.GetObjectIds().Cast<dynamic>()

          where !ent.ExtensionDictionary.Contains("MyDict")

          select ent.ExtensionDictionary;

 

      foreach (dynamic dict in noMyDicts)

        dict.SetAt("MyDict", new DBDictionary());

    }

 

    // Access various bits of information using LINQ

 

    [CommandMethod("IUL")]

    public void InfoUsingLINQ()

    {

      Document doc = Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      dynamic bt = db.BlockTableId;

 

      // Dynamic .NET loop iteration

 

      ed.WriteMessage("\n*** BlockTableRecords in this DWG ***");

      foreach (dynamic btr in bt)

        ed.WriteMessage("\n" + btr.Name);

 

      // LINQ query - returns startpoints of all lines

 

      ed.WriteMessage(

        "\n\n*** StartPoints of Lines in ModelSpace ***"

      );

 

      dynamic ms = SymbolUtilityServices.GetBlockModelSpaceId(db);

      var lineStartPoints =

        from ent in (IEnumerable<dynamic>)ms

          where ent.IsKindOf(typeof(Line))

          select ent.StartPoint;

 

      foreach (Point3d start in lineStartPoints)

        ed.WriteMessage("\n" + start.ToString());

 

      // LINQ query - all entities on layer '0'

 

      ed.WriteMessage("\n\n*** Entities on Layer 0 ***");

 

      var entsOnLayer0 =

        from ent in (IEnumerable<dynamic>)ms

          where ent.Layer == "0"

          select ent;

 

      foreach (dynamic e in entsOnLayer0)

        ed.WriteMessage(

          "\nHandle=" + e.Handle.ToString() + ", ObjectId=" +

          ((ObjectId)e).ToString() + ", Class=" + e.ToString()

        );

      ed.WriteMessage("\n\n");

 

      // Using LINQ with selection sets

 

      PromptSelectionResult res = ed.GetSelection();

      if (res.Status != PromptStatus.OK)

        return;

 

      // Select all entities in selection set that have an object

      // called "MyDict" in their extension dictionary

 

      var extDicts =

        from ent in res.Value.GetObjectIds().Cast<dynamic>()

          where ent.ExtensionDictionary != ObjectId.Null &&

            ent.ExtensionDictionary.Contains("MyDict")

          select ent.ExtensionDictionary.Item("MyDict");

 

      // Erase our dictionary

 

      foreach (dynamic myDict in extDicts)

        myDict.Erase();

    }

  }

}

You'll see that certain operations become very straightforward when combining Dynamic .NET and LINQ. This could well be the "sweet spot" of the dynamic capability – it certainly makes LINQ a more natural choice when accessing various types of data in AutoCAD drawings.

That's it for today's post. Next time we'll summarize the remaining API enhancements coming in AutoCAD 2013, before looking at the migration steps needed for .NET applications.

23 responses to “Dynamic .NET in AutoCAD 2013”

  1. I like duck typing: "When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck" (from Wikipedia). It looks like .NET will eventualy be usable by mere human beings like me and not only by computer scientists đŸ˜‰
    The future is not in exposing the computer internal guts but is in hiding them and making them work for your average programmer. Your first example is madness, your second one is acceptable.

  2. Thorsten Meinecke Avatar
    Thorsten Meinecke

    ...migration steps needed for .NET applications...

    Ouch. I hope you aren't saying I can't netload our existing code base that was created over the last 6 years (since rel. 2007)?

  3. You can leverage the same codebase (almost exactly), but you will need a new DLL.

    Kean

  4. Stephen Preston Avatar
    Stephen Preston

    I feel obliged to emphasize that my code that Kean quoted was me testing whether these things could be done using Dynamic .NET and LINQ. Just because you can do something doesn't mean you should. :-s

    And to get my excuses in early - this was also my first experiment with LINQ in any context :-).

    Cheers,

    Stephen

  5. Dynamic .NET is goot, but what about dynamic properties in AutoCAD? Any new interfaces to OPM?

  6. Well, thanks for (unintentionally) showing how horribly inefficient duck typing/late binding can be.

    In your first example (prefixing a layer name) you reference the layer's property 3 times (2 reads and 1 write). Of course, if one understands the overhead of each dynamic invocation, they would probably also understand the importance of minimizing them, which in the case of your example would mean storing the layer name in a variable and operating on that, rather than the Name property directly.

    Cheers :d

  7. Kean Walmsley Avatar

    Yes, we're accessing the information three times. We could certain store the information in a variable and access it twice, but the question is whether it's worth it.

    Given the purpose of this particular code, you're likely to hit limits on the number of layers in a drawing before you see performance issues. But if you see a noticeable lag, feel free to use static typing.

    Kean

  8. Kean Walmsley Avatar

    I'll be covering something related to "non-COM properties", at some point, but as it's C++ only (and therefore of interest to a subset of this blog's readership), I'm unlikely to go into significant detail.

    Kean

  9. Hi,Kean
    Can I change the entities' color like this with AutoCAD 2013:
    public void Test()
    {
    PromptSelectionResult res = Application.DocumentManager.MdiActiveDocument.Editor.GetSelection();
    if (res.Status != PromptStatus.OK)
    return;
    var ents = from ent in res.Value.GetObjectIds().Cast<dynamic>()
    where ent.Layer == "0"
    select ent;
    foreach (dynamic ent in ents)
    ent.ColorIndex=1;
    }

  10. Kean Walmsley Avatar

    I haven't specifically run your code, but yes, something like it should work.

    Kean

  11. Hi Kean,
    This seems like a convenient place to thank ymou for your IronPython articles. I'm a lowly CAD Operator in a small design office. The lack of language ceremony in Python, and a few facade classes, makes tasks that might have been drudgery before into interesting automation problems.

  12. "there is no IntelliSense available when coding dynamically (something Python and Ruby developers are used to.... It would not be impossible to implement, but it would be very difficult"

    It would be impossible. Without an instance, there is no way to know what the actual type is, and without knowing the type, there's no way to reflect on its members. That's just the nature of the beast.

  13. Kean Walmsley Avatar

    It's my pleasure. đŸ™‚

    Kean

  14. Kean Walmsley Avatar

    You could well be right.

    I like to think that someone might find a way to at least provide IntelliSense for cases where types can be inferred, in some way (constructors and functions that return objects of a specific type are candidates, for example).

    It might not be complete (or consistent), but it'd be something.

    Kean

  15. A very interesting article about dynamic .NET in AutoCAD 2013 by Serge Camiré (in french): goo.gl/VI5GN

    1. It says file not found

  16. The problem there is that 'dynamic' can mean more than just 'duck typing' or 'late binding' to a statically-defined set of properties, because the set of properties for each instance of an object could be dynamic as well, meaning that some instances of a given type of object can have properties that other instances of the same type don't have (just like the 'per-instance' properties that can be defined for the OPM).

    'Dynamic' could also mean that when you try to assign a value to a property that currently doesn't exist on an instance, rather than getting an error, the instance might just say "hmmm, that property doesn't exist - Ok, let's add it now". The System.Dynamic.ExpandoObject is a good example of that. It's essentially just an IDictionary of name/value pairs that exposes the values as dynamic properties whose names are the keys. That lets you add new properties to an instance at runtime, on a per-instance basis.

    While the 'dynamic ObjectId' is a novel idea, I think its usefulness is limited given the heavy dependence most have on IntelliSense.

    And, I think there's other, far-better ways to leverage dynamic programming in AutoCAD's managed API.

    For example, IDynamicMetaObjectProvider can be used to expose AutoCAD block reference attributes, or AutoCAD dynamic block properties as dynamic properties of the BlockReference managed wrapper, that can be referenced in code (realistically, they'd be instances of some dyanmic object that itself would be a property of the BlockReference class, to avoid name collisons with statically-declared properties).

    Those applications of the 'dynamic' keyword would be useful in strongly-typed AutoCAD programming as well.

  17. Kean Walmsley Avatar

    Thanks, Patrick! I'd also received this by email, but I'm happy the link is posted here for others to access.

    Kean

  18. Kean Walmsley Avatar

    I agree: as I mentioned in the post, the lack of IntelliSense (which I'm not expecting to be addressed anytime soon) is going to be a limitation for many. This could be mitigated - as for other dynamic environments - by increasing the discoverability of the language by some kind of REPL, but it still won't be suited to many people. Which is fair enough - there's no requirement to use it.

    Your suggestion around block attributes is interesting. It's not clear this is a "far-better" use of dynamic programming than the base ObjectId exposure, but it's certainly interesting. It reminds me of the work that the F# team has done on Type Providers, which might be another approach for navigating the AutoCAD drawing structure.

    By the way - is that you, Tony?

    Kean

  19. Michael Schumacher Avatar
    Michael Schumacher

    What if any ability is there to mix transactions and dynamic objects? What is the result, say in the case of looping over a set of entities changing properties and maybe you hit an object on a locked layer and it can't be edited? Do you blow out and leave half of your process complete?

    Can you start a transaction, put your dynamic code inside and use the traditional commit/rollback on the transaction to manage the operations inside the transaction?

  20. Kean Walmsley Avatar

    I'd expect some kind of exception to be thrown if you attempt to edit something that cannot be (although I admit to not having tried it, as yet).

    And we've always advised against mixing transactions with open/close, which I assume remains valid advice for dynamic + transactions.

    Kean

  21. Mr. Walmsley:
    I'm a rookie and I wonder what's the best choise to start with.ObjectARX,or .NET? I'm waiting for your answer.Thank U very much.

  22. If you don't already know C++ (which you need for ObjectARX) then I would always say .NET.

    This should help you get started:

    autodesk.com/myfirstautocadplugin

    Kean

Leave a Reply

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