Kean Walmsley


  • About the Author
    Kean on Google+

August 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
31            








« Asynchronous messages in F# and AutoCAD | Main | My Autodesk University 2009 classes available via AU Online »

December 09, 2009

Command-line scripting of IronPython code in AutoCAD

This post was heavily inspired by the code presented by my old friend Albert Szilvasy during his excellent AU class on using .NET 4.0 with AutoCAD.

Albert took a different approach to the one I’ve previously adopted (which turns out also to have been suggested by Albert, when I look back at my original post), and created a palette to host IronPython code inside AutoCAD, enabling the ability to enter code directly in AutoCAD rather than relying on an external text file.

In this post we’ll take Albert’s technique and implement a command-line interface for querying and executing IronPython script. This approach could also be adapted to work with other DLT languages such as IronRuby, of course.

Here’s the updated C# code which now not only implements PYLOAD functionality, but also a PYEXEC command:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Ribbon;

 

using IronPython.Hosting;

using Microsoft.Scripting.Hosting;

using System.Collections.Generic;

using System;

 

namespace PythonLoader

{

  public class App : IExtensionApplication

  {

    public void Initialize()

    {

      DemandLoading.RegistryUpdate.RegisterForDemandLoading();

 

      CommandsAndFunctions.InitializeRuntime();

    }

    public void Terminate()

    {

    }

  }

 

  public class CommandsAndFunctions

  {

    // Some state to allow us to incrementally build and

    // execute Python code

 

    private static ScriptEngine _engine = null;

    private static ScriptScope _scope = null;

 

    // Initialization function, called on application load

 

    internal static void InitializeRuntime()

    {

      // Create our base setup for the hosted runtime

 

      ScriptRuntimeSetup setup = new ScriptRuntimeSetup();

 

      // Add our language: the version number may vary

      // depending on the specific version of IronPython

 

      setup.LanguageSetups.Add(

        new LanguageSetup(

          "IronPython.Runtime.PythonContext, IronPython, " +

          "Version=2.6.0.20, Culture=neutral",

          "IronPython 2.6",

          new string[] { "python" }, new string[] { ".py" }

        )

      );

 

      // Create the runtime and load the AcMgd.dll and

      // AcDbMgd.dll assemblies

 

      ScriptRuntime runtime = new ScriptRuntime(setup);

      runtime.LoadAssembly(typeof(DBObject).Assembly);

      runtime.LoadAssembly(typeof(Application).Assembly);

 

      // Now we set our state

 

      _engine = runtime.GetEngine("python");

      _scope = _engine.CreateScope();

 

      // Expose some useful runtime variables

      _scope.SetVariable(

        "ThisDrawing",

        Application.DocumentManager.MdiActiveDocument.AcadDocument

      );

      _scope.SetVariable(

        "ThisDocument",

        Application.DocumentManager.MdiActiveDocument

      );

      _scope.SetVariable(

        "Ribbon",

        RibbonServices.RibbonPaletteSet.RibbonControl

      );

    }

 

    [CommandMethod("PYEXEC")]

    public static void PythonExecute()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      // Stop if we have a state problem (shouldn't happen)

 

      if (_engine == null || _scope == null)

      {

        ed.WriteMessage("\nRuntime needs initializing.");

        return;

      }

 

      // Introduce the environment

 

      ed.WriteMessage("\nInteractive Python scripting...");

 

      string sep = "".PadLeft(80, '_') + "\n";

      bool cont = true;

      string text = "";

 

      // Loop until cancelled or executed

 

      while (cont)

      {

        // Echo the current unexecuted buffer contents

 

        if (text != "")

          ed.WriteMessage(sep + "\nScript buffer:\n" + text + sep);

 

        // Get a line of Python

 

        PromptStringOptions pso =

          new PromptStringOptions("\n>>");

        pso.AllowSpaces = true;

 

        PromptResult pr = ed.GetString(pso);

        if (pr.Status == PromptStatus.OK)

        {

          // If a blank line, execute the buffer

 

          if (pr.StringResult == "")

          {

            try

            {

              _engine.Execute(text, _scope);

              text = "";

              cont = false;

            }

            catch (System.Exception ex)

            {

              ed.WriteMessage("\nError: " + ex.Message);

              cont = false;

            }

          }

          else

          {

            // Otherwise append the string to the buffer

 

            text += pr.StringResult + "\n";

          }

        }

        else

        {

          // If anything else but OK was received then

          // stop looping

 

          cont = false;

        }

      }

    }

 

    [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)

      {

        try

        {

          Dictionary<string, object> options =

            new Dictionary<string, object>();

          options["Debug"] = true;

          ScriptEngine engine = Python.CreateEngine(options);

          engine.ExecuteFile(file);

        }

        catch (System.Exception ex)

        {

          Document doc =

            Application.DocumentManager.MdiActiveDocument;

          Editor ed = doc.Editor;

 

          ed.WriteMessage(

            "\nProblem executing script: {0}", ex.Message

          );

        }

      }

      return ret;

    }

  }

}

As we’re also making use of the Ribbon component, you’ll probably have to add some additional assembly references, such as to AcWindows.dll, AdWindows.dll, PresentationCore, PresentationFramework and WindowsBase (as well as the standard references to AcDbMgd.dll, AcMgd.dll, IronPython.dll, IronPython.Modules.dll, Microsoft.Scripting.dll and Microsoft.Scripting.Code.dll (phew! :-)).

When we execute the PYEXEC command, we get presented with a message on the AutoCAD command-line:

Command: PYEXEC

Interactive Python scripting...

>>:

This is where we can type (or paste) in lines of IronPython code. After each line is entered the unexecuted code in the buffer is echoed to the command-line. To execute the buffer, simply enter return on an empty line.

So if we paste in some code to minimise all the ribbon panels:

for panel in Ribbon.Tabs[0].Panels :
     panel.IsCollapsed = True

We see this:

Command: PYEXEC

Interactive Python scripting...

>>: for panel in Ribbon.Tabs[0].Panels:

________________________________________________________________________________

 

Script buffer:

for panel in Ribbon.Tabs[0].Panels:

________________________________________________________________________________

 

>>:  panel.IsCollapsed = True

________________________________________________________________________________

 

Script buffer:

for panel in Ribbon.Tabs[0].Panels:

  panel.IsCollapsed = True

________________________________________________________________________________

 

>>:

When we then enter a blank line (hitting return directly), the code gets executed and the panels on the first ribbon tab are all minimised:

Collapsed ribbon

The actual interaction implementation could certainly use some refinement, but this is really about the principle rather than the specific details. This sample should prove of use for people who want to just play around with Python inside AutoCAD, to get a better handle on the possibilities of .NET-related scripting inside the product.

blog comments powered by Disqus

10 Random Posts