I've been meaning to play around with the Python language for somePython Logo time, now, and with the recent release of IronPython 2 it seems a good time to start.

Why Python? A number of people in my team – including Jeremy Tammik and the people within our Media  & Entertainment workgroup who support Python's use with Maya and MotionBuilder – are fierce proponents of the language. I'm told that it's an extremely easy, general-purpose, dynamic programming language. All of which sounds interesting, of course, although I have to admit I'm less convinced of the importance of the dynamic piece: I've found a lot of value in static typing over the years (even F# is statically typed, although many people – even some who work with it - don't realise this… its type inference system allows you to code safely without specifying types all over the place).

Let's take a quick step back and talk about what makes a language dynamic. The most common example of a dynamic language – one that I'm sure most of you will have touched at some point – is JavaScript. In JavaScript you declare everything as a var, assign it, call methods on it and hope that they work at runtime. I admit that I've always disliked developing in JavaScript because of the lack of decent tool support: I'm a big fan of Intellisense (based on an object's design-time type) and want the compiler to tell me if I'm dealing with an object that doesn't support a particular method. But perhaps that's largely what I've become used to from modern development tools, and I'm trying to remain open to new things. Really, I am.

Another dynamic language with which I've had much more favourable (but still, at times, frustrating) experiences is LISP. But my relationship with LISP is different: like most early AutoCAD programmers I adopted it out of necessity – and at the time I started with it programming environments were, in any case, generally very basic - I then grew to love it and have since never forgotten it, even when more attractive/productive development environments came along. So I'm extremely loathe to paint it with the same brush as the one I've used for JavaScript.

Python is also of interest because of its cross-platform availability: it's an open source language with its roots in the UNIX/Linux world, but is now gaining popularity across a variety of OS platforms (one of the reasons it's the scripting language chosen for at least one of our cross-platform products, Autodesk Maya).

Microsoft is definitely now very open to the possibilities of dynamic languages: they're making a significant investment in the Dynamic Language Runtime, to support languages such as IronPython and IronRuby, as well as adding more dynamic features with C# 4.0 (which is finally going to get something comparable to VB's "late binding" capability).

So all in all, the world we live in seems to be becoming increasingly dynamic. 🙂

Anyway – now on to getting IronPython working with AutoCAD. I had originally hoped to build a .NET assembly directly using IronPython – something that appears to have been enabled with the 2.0 release of IronPython - which could then be loaded into AutoCAD. Unfortunately this was an exercise in frustration: AutoCAD makes heavy use of custom attributes for identifying commands etc., but IronPython doesn't currently support the use of attributes. It is possible to do some clever stuff by compiling attributed C# on-the-fly and deriving classes from it (information on this is available here), which will – in theory, at least – get you something in memory that's attributed but, as AutoCAD scans the physical assembly for custom attributes before loading it, this didn't help. I also spent a great deal of time just trying to derive a class from Autodesk.AutoCAD.Runtime.IExtensionApplication – to have the Initialize() function called automatically on load – but I just couldn't get this to work, either.

Then, thankfully, Tim Riley came to the rescue: we've been in touch on and off over the years since he started the PyAcad.NET project to run IronPython code inside AutoCAD, and Tim was able to put together some working code which actually registered commands (after I'd pointed him at a function he could use from AutoCAD 2009's acmgdinternal.dll – an unsupported assembly that exposes some otherwise quite helpful functions). He ended up choosing an implementation that had also been suggested to me by Albert Szilvasy: to implement a PYLOAD command using C# which allows selection and loading of a Python script (because Python is, ultimately, all about scripting rather than building static, compiled assemblies).

Before we get on to the C# module, I should point out that I installed IronPython 2.0.1 as well as IronPython Studio 1.0 for the Visual Studio 2008 integration. It turns out that as we're relying on C# to manage the loading of Python – rather than compiling a .NET assembly – the main advantage of IronPython Studio is around the ability to work with Python source code inside Visual Studio.

To build the below C# code into a standard .NET Class Library assembly (a .DLL) you'll need to add assembly references to IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll and Microsoft.Scripting.Core.dll – all of which can be found in the main IronPython install folder (on my system this is in "C:\Program Files\IronPython 2.0.1"). As well as the standard references to acmgd.dll and acdbmgd.dll, of course.

Here's the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.EditorInput;

using IronPython.Hosting;

using Microsoft.Scripting.Hosting;

using System;

 

namespace PythonLoader

{

  public class CommandsAndFunctions

  {

    [CommandMethod("-PYLOAD")]

    public static void PythonLoadCmdLine()

    {

      PythonLoad(true);

    }

 

    [CommandMethod("PYLOAD")]

    public static void PythonLoadUI()

    {

      PythonLoad(false);

    }

 

    public static void PythonLoad(bool useCmdLine)

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      short fd =

        (short)Application.GetSystemVariable("FILEDIA");

 

      // As the user to select a .py file

 

      PromptOpenFileOptions pfo =

          new PromptOpenFileOptions(

            "Select Python script to load"

          );

      pfo.Filter = "Python script (*.py)|*.py";

      pfo.PreferCommandLine =

        (useCmdLine || fd == 0);

      PromptFileNameResult pr =

        ed.GetFileNameForOpen(pfo);

 

      // And then try to load and execute it

 

      if (pr.Status == PromptStatus.OK)

        ExecutePythonScript(pr.StringResult);

    }

 

    [LispFunction("PYLOAD")]

    public ResultBuffer PythonLoadLISP(ResultBuffer rb)

    {

      const int RTSTR = 5005;

 

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      if (rb == null)

      {

        ed.WriteMessage("\nError: too few arguments\n");

      }

      else

      {

        // We're only really interested in the first argument

 

        Array args = rb.AsArray();

        TypedValue tv = (TypedValue)args.GetValue(0);

 

        // Which should be the filename of our script

 

        if (tv != null && tv.TypeCode == RTSTR)

        {

          // If we manage to execute it, let's return the

          // filename as the result of the function

          // (just as (arxload) does)

 

          bool success =

            ExecutePythonScript(Convert.ToString(tv.Value));

          return

            (success ?

              new ResultBuffer(

                new TypedValue(RTSTR, tv.Value)

              )

              : null);

        }

      }

      return null;

    }

 

    private static bool ExecutePythonScript(string file)

    {

      // If the file exists, let's load and execute it

      // (we could/should probably add some more robust

      // exception handling here)

 

      bool ret = System.IO.File.Exists(file);

      if (ret)

      {

        ScriptEngine engine = Python.CreateEngine();

        engine.ExecuteFile(file);

      }

      return ret;

    }

  }

}

The code behind the PYLOAD command is actually really simple. I could have kept it basic but decided it would be a good opportunity to show some best practices. So not only do we have the standard PYLOAD command, which respects the FILEDIA variable to decide whether to use dialogs or the command-line, we also have a command-line version –PYLOAD and a LISP function (pyload). All of which call into the same function to load a Python script.

OK, now let's take a look at a simple IronPython script that calls into AutoCAD via its .NET API. Thanks again to Tim Riley for providing something that works. Even with Python being (apparently) so easy to learn, I'm such a neophyte that without his help I'd still be stumbling around in the dark.

import clr

path = 'C:\\Program Files\\Autodesk\\AutoCAD 2009\\'

clr.AddReferenceToFileAndPath(path + 'acdbmgd.dll')

clr.AddReferenceToFileAndPath(path + 'acmgd.dll')

clr.AddReferenceToFileAndPath(path + 'acmgdinternal.dll')

 

import Autodesk

import Autodesk.AutoCAD.Runtime as ar

import Autodesk.AutoCAD.ApplicationServices as aas

import Autodesk.AutoCAD.DatabaseServices as ads

import Autodesk.AutoCAD.Geometry as ag

import Autodesk.AutoCAD.Internal as ai

from Autodesk.AutoCAD.Internal import Utils

 

# Function to register AutoCAD commands

# To be used via a function decorator

 

def autocad_command(function):

 

    # First query the function name

    n = function.__name
__

 

    # Create the callback and add the command

    cc = ai.CommandCallback(function)

    Utils.AddCommand('pycmds', n, n, ar.CommandFlags.Modal, cc)

 

    # Let's now write a message to the command-line

    doc = aas.Application.DocumentManager.MdiActiveDocument

    ed = doc.Editor

    ed.WriteMessage("\nRegistered Python command: {0}", n)

 

# A simple "Hello World!" command

 

@autocad_command

def msg():

    doc = aas.Application.DocumentManager.MdiActiveDocument

    ed = doc.Editor

    ed.WriteMessage("\nOur test command works!")

 

# And one to do something a little more complex...

# Adds a circle to the current space

 

@autocad_command

def mycir():

 

    doc = aas.Application.DocumentManager.MdiActiveDocument

    db = doc.Database

 

    tr = doc.TransactionManager.StartTransaction()

    bt = tr.GetObject(db.BlockTableId, ads.OpenMode.ForRead)

    btr = tr.GetObject(db.CurrentSpaceId, ads.OpenMode.ForWrite)

 

    cir = ads.Circle(ag.Point3d(10,10,0),ag.Vector3d.ZAxis, 2)

 

    btr.AppendEntity(cir)

    tr.AddNewlyCreatedDBObject(cir, True)

 

    tr.Commit()

    tr.Dispose()

As we're stuck without the ability to use custom attributes in IronPython, we're making use of the Autodesk.AutoCAD.Internal namespace to register commands at runtime. I don't like doing this, but at the same time I was left with little choice, unless we choose to find another way to call into the code. Please be warned that anything contained in the Autodesk.AutoCAD.Internal namespace is unsupported functionality, and subject to change without warning.

Now that I have that off my chest, let's comment a little further on the above code…

  • Even without custom attributes, we have used a pretty cool Python language feature known as decorators (thanks *again* for the tip, Tim 🙂 which helps us to mark functions as commands. The autocad_command function is called for each decorated function, and this is where we register a command for the function based on the function's name. Pretty cool.
  • You'll notice a distinct lack of types in the code (and yes, that still scares me). When I was previously trying to compile a DLL based on this code, I had a lot of trouble getting anything at all to fail at compile-time, but clearly a lot would fail at runtime (when I could actually get anything to execute :-S). I feel as though I still need to get my head around this trade-off: I can see the argument for simplicity/elegance/succinctness – and even the power it brings in some situations - but the Computer Scientist in me is screaming for safety/reliability/determinism/debuggability (if that's even a word). Oh well. The main thing is that I'm starting the journey, at least: we'll see if it ends up somewhere I like. 🙂

When we build and NETLOAD our PythonLoader C# application and execute the PYLOAD command, we can select our Python script:

Command: PYLOAD

File selection during the PYLOAD command

Once selected, the script gets loaded and should register a couple of commands:

Registered Python command: msg

Registered Python command: mycir

Running the MSG command will execute a simple "Hello World!"-like function, just printing a message to the command-line:

Command: MSG

Our test command works!

And running the MYCIR command should just add a simple circle to the current space in the active drawing.

Command: MYCIR

Results of the MYCIR command

That's it for my initial foray into the world of Python. I hope you've found this helpful and enjoy playing around with the Python programming language inside AutoCAD. Please do post a comment if you have experiences or anecdotes to share on this topic!

2 responses to “Using IronPython with AutoCAD”

  1. Fernando Malard Avatar
    Fernando Malard

    Kean,

    What about IronRuby? Should follow the same scenario, right?

  2. That's right, although I haven't tried it myself (as yet :-).

    Kean

  3. Fernando. IronRuby, or any DLR language, should be possible with minor modifications to the C# code.

    When I get my hands on a non-trial version of 2009 PyAcad.NET will be replaced with a bunch of helpers for working with DLR languages and AutoCAD.

  4. "Tim was able to put together some working code which actually registered commands (after I’d pointed him at a function he could use from AutoCAD 2009’s acmgdinternal.dll – an unsupported assembly that exposes some otherwise quite helpful functions)."

    That's odd. I was under the impression he already knew about that one, from this post:

    theswamp.org/ind...

  5. I'm not sure how you can know Tim saw that thread: he was certainly surprised when I mentioned this functionality to him.

    Kean

  6. I wish I had caught Tony's post back in November as I'd be much further along, but I had missed that one. It was posted 4 days after I got married and I wasn't doing much internet browsing at that time.

  7. "I'm not sure how you can know Tim saw that thread: he was certainly surprised when I mentioned this functionality to him."

    Because it's two posts below his last post in the same thread ?

  8. Sorry, I should have mentioned that on that board, he goes by the user name 'r'

  9. Hi,my name is Jade, I am from Shanghai,I am a lisp beginner.I accidently discovered your blog.I saw your previuos post about the draworder,wondering if you can help us to make a lisp which can decide the each draw order,like layer 1 above layer 2,layer 2 above layer 3,layer 3 above layer 4,etc.
    Thanks a lot

  10. Sorry, Jade: please redirect your question to one of our discussion groups, where someone should be able to help you.

    I do take requests for posts - when I feel they're relevant to a broad audience - but I tend not to develop detailed samples in LISP, these days.

    Kean

  11. Tony:

    I'm not sure what exactly you're looking for, an acknowledgement that you had figured this out first before Kean pointed it out to me? I explicitly stated that I didn't see your post as it was posted a couple days after I was married. I had been trying to accomplish registering commands from IronPython for ~3 years, don't you think I would have at least replied to your post or actually attempted it had I saw it?

  12. Hi there, Thanks for the codes.

  13. I found this code cannot run in autocad2014,can you help me ?give me new code for autocad2014?

  14. Hi,

    What is it that doesn't work in AutoCAD 2014? I don't really have time to look into this, right now, but may be able to give you some pointers if you provide more information.

    Kean

  15. i am so sorry!i have run success.i write wrong path yestory.thank you help!

  16. Not a problem - I'm happy to know it still works in AutoCAD 2014. 🙂

    Kean

  17. Hi,

    Iam using IronPython version 2.7.
    I created dll file as per the instruction given in this link.
    When I try to NETLOAD this dll file in autocad command prompt it gives an error like mentioned below :
    "Cannot load assembly. Error details: System.BadImageFormatException: Could not
    load file or assembly 'file:///C:\PythonLoader.dll' or one of its dependencies. and so on..."

    1. You probably need to make sure the IronPython runtime is available (CopyLocal == True for that assembly).

      Kean

  18. konstantinosmavridis Avatar
    konstantinosmavridis

    I am using AutoCAD 2015 and instead of loading "acmgdinternal.dll" (not working) I load the following dlls "accoremgd.dll" and "acdbmgdbrep.dll" like so

    #clr.AddReferenceToFileAndPath(path + 'acmgdinternal.dll')
    clr.AddReferenceToFileAndPath(path + 'accoremgd.dll')
    clr.AddReferenceToFileAndPath(path + 'acdbmgdbrep.dll')

    I am not sure if both dlls are needed, so I load them both.

    I hope this helps.

    1. Hello friend. Please How I can install iron python into autocad with these files. I don't know how exactly do this part... Thanks very much

  19. Georgy Sokolov Avatar
    Georgy Sokolov

    Dear Kean, thank you for the article. I'm on my start of learning programming for autocad and I have a question for you. Is it possible to compile python scripts to dll assemlies and to import it to c# code using some procedure? I'm thinking of having standalone applications for autocad made this way.

    1. Kean Walmsley Avatar

      I'm not exactly sure what you're asking... are you trying to migrate Python to C#, or have both languages in the same assembly? Or perhaps something different?

      Kean

      1. Georgy Sokolov Avatar
        Georgy Sokolov

        The question is if it is possible to run(host) compilled ironpython dll assemblies from c# code(not script files like *.py).
        Autodesk Revit somehow runs compiled ironpython macroses dll. I wonder how does it work.

        1. Kean Walmsley Avatar

          For a .NET application to run IronPython code, it needs to host the DLR. (At least that's how it used to work.) So the C# module can host the DLR and load the Python script from somewhere, whether a text file or a DLL resource (I'm just guessing that's how Revit does it, it may use some other technique).

          Kean

  20. Subir Kumar Dutta Avatar

    If the python file has import statement to import external library, say,

    from pyautocad import Autocad, APoint

    do I have to do anything special ?

    I am getting below error in C#.

    IronPython.Runtime.Exceptions.ImportException: 'No module named pyautocad'

    Actually I want to make a dock-able palette in AutoCAD using c#.Net with simply a multiline text box and a run button so that user can write python code using pyautocad in the textbox and run it by clicking the run button to see the results immediately into AutoCAD without any need to compile. I just save the content on the multiline text box into a python file and then execute the file using IronPython scripting engine.

    I tried importing the module as below in C#.

    ScriptEngine engine = Python.CreateEngine();
    engine.ImportModule("pyautocad");
    engine.ExecuteFile(file);

    But exception comes as IronPython.Runtime.Exceptions.ImportException: 'no module named pyautocad'

    Then I tried adding the path of "pyautocad" but it results into another exception

    But exception comes as IronPython.Runtime.Exceptions.ImportException: 'no module named logging'

    I believe there must be some better way but as I am new to python-c# interaction, I can't make it work as of now.

    1. Hi Subir,

      Please post your support questions to the AutoCAD .NET forum.

      Thanks,

      Kean

  21. Taya Blackrose Avatar
    Taya Blackrose

    @keanw:disqus Autodesk.AutoCAD.Internal workaround doesn't work anymore in latest versions of AutoCAD. Is there a way to do a custom C++ wrapper to take leverage of existing AcEdCommandStack?

    1. Kean Walmsley Avatar

      Hi Taya,

      I expect there is, but it's been quite some time since I've done anything like this. I suggest posting to the AutoCAD .NET or the ObjectARX forum, to see if someone there can help.

      Best,

      Kean

Leave a Reply

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