Kean Walmsley


  • About the Author
    Kean on Google+

July 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    








« Through the Interface moving from TypePad to Twitter | Main | Jigging an AutoCAD solid using IronRuby and .NET (well, almost) »

April 02, 2009

Using IronRuby with AutoCAD

[I’ve now started pushing links to my posts out through Twitter, even if I haven’t gone quite so far as to abandon TypePad (yes, it was an April Fools’ joke, in case anyone missed the closing comment :-)].

Having spent some time looking into Python, I decided to give Ruby – another popular scripRuby Logoting language and one with an “Iron” implementation allowing you to work with .NET – the same treatment.

From what I can tell – and I’m really a newbie in both these languages – there is relatively little to separate the two: both Ruby and Python have their devotees but they ultimately to belong to different sects of the same faith (it’s hard to escape religious analogies when talking about programming languages, for some reason :-). That said, the fiercest arguments often seem to occur between people who very nearly agree. This is not something I know enough about to get involved in – even if I wished to – so I’m going to steer well clear of it, and simply show some simple Ruby code that does the same as in the equivalent post for IronPython. If you Google “python ruby comparison” you should find plenty of opinions out there.

To get started I installed the most recently released version of IronRuby at the time of writing, version 0.3.

As with IronPython, we’re going to use a C# loader inside AutoCAD to host the IronRuby runtime and use it to interpret a Ruby script. The code to do this is very similar to that needed for IronPython, and it should, in fact, be simple enough to templatize the code in some way and combine the two into a single implementation (something I expect Tim Riley will be doing with PyAcad.NET, when he gets the chance). I’m leaving the implementations separate, for now, to make it easier for people to work with the languages independently.

Here is the C# code defining our RBLOAD command. You’ll need to add project references to IronRuby.dll, IronRuby.Libraries.dll, Microsoft.Scripting.dll and Microsoft.Scripting.Core.dll, all of which can be found in IronRuby’s bin folder. You’ll clearly also need to reference the usual acmgd.dll and acdbmgd.dll.

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.EditorInput;

using IronRuby.Hosting;

using IronRuby;

using Microsoft.Scripting.Hosting;

using System;

 

namespace RubyLoader

{

  public class CommandsAndFunctions

  {

    [CommandMethod("-RBLOAD")]

    public static void RubyLoadCmdLine()

    {

      RubyLoad(true);

    }

 

    [CommandMethod("RBLOAD")]

    public static void RubyLoadUI()

    {

      RubyLoad(false);

    }

 

    public static void RubyLoad(bool useCmdLine)

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      short fd =

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

 

      // As the user to select a .rb file

 

      PromptOpenFileOptions pfo =

          new PromptOpenFileOptions(

            "Select Ruby script to load"

          );

      pfo.Filter = "Ruby script (*.rb)|*.rb";

      pfo.PreferCommandLine =

        (useCmdLine || fd == 0);

      PromptFileNameResult pr =

        ed.GetFileNameForOpen(pfo);

 

      // And then try to load and execute it

 

      if (pr.Status == PromptStatus.OK)

        ExecuteRubyScript(pr.StringResult);

    }

 

    [LispFunction("RBLOAD")]

    public ResultBuffer RubyLoadLISP(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 =

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

          return

            (success ?

              new ResultBuffer(

                new TypedValue(RTSTR, tv.Value)

              )

              : null);

        }

      }

      return null;

    }

 

    private static bool ExecuteRubyScript(string file)

    {

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

 

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

      if (ret)

      {

        try

        {

          ScriptEngine engine = Ruby.CreateEngine();

          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;

    }

  }

}

Here’s my first attempt at a Ruby script which does basically the same as my original attempt for IronPython. I did find that not every .rb file was loadable by the RBLOAD command - presumably it only accepts certain text encoding standards – so I ended up pasting the code into a copy of one of the standard .rb files shipping in the IronRuby lib folder.

require 'C:\Program Files\Autodesk\AutoCAD 2009\acmgd.dll'

require 'C:\Program Files\Autodesk\AutoCAD 2009\acdbmgd.dll'

require 'C:\Program Files\Autodesk\AutoCAD 2009\acmgdinternal.dll'

 

# Function to register AutoCAD commands

 

def autocad_command(name) 

  cc =

    Autodesk::AutoCAD::Internal::CommandCallback.new method(name)

  Autodesk::AutoCAD::Internal::Utils.AddCommand(

    'rbcmds', name, name,

    Autodesk::AutoCAD::Runtime::CommandFlags.Modal, cc)

 

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

 

  app = Autodesk::AutoCAD::ApplicationServices::Application

  doc = app.DocumentManager.MdiActiveDocument

  ed = doc.Editor

  ed.WriteMessage("\nRegistered Ruby command: {0}", name)

end

 

def add_commands(names)

  names.each { |n| autocad_command n }

end

 

# A simple "Hello World!" command

 

def msg

  app = Autodesk::AutoCAD::ApplicationServices::Application

  doc = app.DocumentManager.MdiActiveDocument

  ed = doc.Editor

  ed.WriteMessage "\nOur test command works!"

end

 

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

# Adds a circle to the current space

 

def mycir

  app = Autodesk::AutoCAD::ApplicationServices::Application

  doc = app.DocumentManager.MdiActiveDocument

  db = doc.Database

 

  tr = doc.TransactionManager.StartTransaction

  bt =

    tr.GetObject(

      db.BlockTableId,

      Autodesk::AutoCAD::DatabaseServices::OpenMode.ForRead)

  btr =

    tr.GetObject(

      db.CurrentSpaceId,

      Autodesk::AutoCAD::DatabaseServices::OpenMode.ForWrite)

  cir =

    Autodesk::AutoCAD::DatabaseServices::Circle.new(

      Autodesk::AutoCAD::Geometry::Point3d.new(10, 10, 0),

      Autodesk::AutoCAD::Geometry::Vector3d.ZAxis, 2)

 

  btr.AppendEntity(cir)

  tr.AddNewlyCreatedDBObject(cir, true)

 

  tr.Commit

  tr.Dispose

end

 

add_commands ["msg", "mycir"]

The code differs slightly in approach than the corresponding Python script: I wasn’t aware of an equivalent method for Python decorators and so used a more explicit approach for adding AutoCAD commands. The code uses the same internal (and unsupported) assembly, acmgdinternal.dll, to register the commands dynamically with AutoCAD – that piece is basically the same – it’s just the method of choosing the functions to register that differs (see the call to add_commands() at the bottom of the script).

Otherwise the code is very similar, aside from the use of namespace aliases in IronPython (something that may very well exist in IronRuby – I just haven’t come across it yet).

The execution is analogous to the Python version. When we build and NETLOAD our RubyLoader C# application and execute the RBLOAD command, we can select our Ruby script:

Command: RBLOAD

The RBLOAD command

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

Registered Ruby command: msg

Registered Ruby 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

TrackBack

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

Listed below are links to weblogs that reference Using IronRuby with AutoCAD:

blog comments powered by Disqus

10 Random Posts