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            








« Using IronRuby with AutoCAD | Main | Customizing the display of standard AutoCAD objects using .NET »

April 06, 2009

Jigging an AutoCAD solid using IronRuby and .NET (well, almost)

In the last post I introduced a very simple “Hello World!” IronRuby application working with AutoCAD, just as I’d previously done with IronPython. My idea for this post was to take the code from my second IronPython post – which showed how to jig an AutoCAD solid from IronPython – and get it working with IronRuby, forcing me to learn a little more Ruby in the process.

All started out well: to convert the basic syntax from Python to Ruby was straightforward, and I have a definite liking for the syntax of the Ruby language. Especially when working with object orientation: the code for implementing classes feels cleaner than Python and it’s really OO from top to bottom (everything’s an object). I even found a way to avoid all those namespaces everywhere in the code – you just assign them to symbols and use those intead.

So what didn’t work? Well, it turns out there’s a problem with the definition of the EntityJig class, which is the parent of our SolidJig: IronRuby tells me it doesn’t have a default constructor defined. It’s very similar in nature to the problem I had deriving the IronPython version of my SolidJig class, but in this case I had to make sure my code very specifically implemented an __init__ function taking an entity (IronRuby uses initialize() instead of __init__ and is really clean in the way it allows you to super-message to your parent class, but right now it seems there’s something getting in the way – IronRuby is at version 0.3, after all).

Here’s my IronRuby script – for the RBLOAD command look at the C# code in the last post.

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'

 

Ai =  Autodesk::AutoCAD::Internal

Aiu = Autodesk::AutoCAD::Internal::Utils

Aas = Autodesk::AutoCAD::ApplicationServices

Ads = Autodesk::AutoCAD::DatabaseServices

Aei = Autodesk::AutoCAD::EditorInput

Ag =  Autodesk::AutoCAD::Geometry

Ar =  Autodesk::AutoCAD::Runtime

 

def print_message(msg)

  app = Aas::Application

  doc = app.DocumentManager.MdiActiveDocument

  ed = doc.Editor

  ed.WriteMessage(msg)

end

 

# Function to register AutoCAD commands

 

def autocad_command(cmd) 

  cc = Ai::CommandCallback.new method(cmd)

  Aiu.AddCommand('rbcmds', cmd, cmd, Ar::CommandFlags.Modal, cc)

 

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

 

  print_message("\nRegistered Ruby command: " + cmd)

end

 

def add_commands(names)

  names.each { |n| autocad_command n }

end

 

# Let's do something a little more complex...

 

class SolidJig < Aei::EntityJig

 

  # Initialization function

 

  def initialize(ent)

 

    # Call the base class and store the object

 

    super

    @sol = ent

 

  end

 

  # The function called to run the jig

 

  def StartJig(ed, pt)

 

    # The start point is specified outside the jig

 

    @start = pt

    @end = pt

 

    return ed.Drag(self)

  end

 

  # The sampler function

 

  def Sampler(prompts)

 

    # Set up our selection options

 

    jo = Aei::JigPromptPointOptions.new

    jo.UserInputControls = (

      Aei::UserInputControls.Accept3dCoordinates |

      Aei::UserInputControls.NoZeroResponseAccepted |

      Aei::UserInputControls.NoNegativeResponseAccepted)

    jo.Message = "\nSelect end point: "     

 

    # Get the end point of our box

 

    res = prompts.AcquirePoint(jo)

 

    if @end == res.Value

      return Aei::SamplerStatus.NoChange

    else

      @end = res.Value

    end

 

    return Aei::SamplerStatus.OK

  end

 

  # The update function

 

  def Update()

 

    # Recreate our Solid3d box

 

    begin

 

      # Get the width (x) and depth (y)

 

      x = @end.X - @start.X

      y = @end.Y - @start.Y

 

      # We need a non-zero Z value, so we copy Y

 

      z = y

 

      # Create our box and move it to the right place

 

      @sol.CreateBox(x,y,z)

      @sol.TransformBy(

        Ag::Matrix3d.Displacement(

          Ag::Vector3d.new(

            @start.X + x/2,

            @start.Y + y/2,

            @start.Z + z/2)))

    rescue

      return false

    end 

    return true

  end

end

 

# Create a box using a jig

 

def boxjig

 

  app = Aas::Application

  doc = app.DocumentManager.MdiActiveDocument

  db = doc.Database

  ed = doc.Editor

 

  # Select the start point before entering the jig

 

  ppr = ed.GetPoint("\nSelect start point: ")

 

  if ppr.Status == Aei::PromptStatus.OK

 

    # We'll add our solid to the modelspace

 

    tr = doc.TransactionManager.StartTransaction

    bt =

      tr.GetObject(

        db.BlockTableId,

        Ads::OpenMode.ForRead)

    btr =

      tr.GetObject(db.CurrentSpaceId,Ads::OpenMode.ForWrite)

 

    # Make sure we're recording history to allow grip editing

 

    sol = Ads::Solid3d.new

    sol.RecordHistory = true

 

    # Now we add our solid

 

    btr.AppendEntity(sol)

    tr.AddNewlyCreatedDBObject(sol, true)

 

    # And call the jig before finishing

 

    begin

 

      sj = SolidJig.new sol

 

      ppr2 = sj.StartJig(ed, ppr.Value)

 

      # Only commit if all completed well

 

      if ppr2.Status == Aei::PromptStatus.OK

        tr.Commit

      end

 

    rescue

 

      print_message("\nProblem found: " + $! + "\n")

 

    end

 

    tr.Dispose

  end

end

 

add_commands ["boxjig"]

When we run the RBLOAD command and select this script, our BOXJIG command gets defined.

Registered Ruby command: boxjig

When this is run, our Begin-Rescue block (equivalent to Try-Catch in most other modern programming languages :-) catches the exception and prints an error statement at the command-line:

Command: BOXJIG

Select start point:

Problem found: Parent does not have a default constructor. The default constructor must be explicitly defined.

I’m hoping the root cause of the issue is obvious to someone familiar with IronRuby, whether in the above calling code, the design of the API exposed by AutoCAD or the implementation of IronRuby itself.

I do prefer not to post code that doesn’t actually work, but I feel there’s still value in seeing the comparison between the Python and Ruby scripts, for people to get a feel for how the languages differ. And - apart from anything else – I’ve hit my head against this for long enough that I at least want to get a post out of it. :-)

Update:

Ivan Porto Carrero – who’s working on the book IronRuby in Action – is able to reproduce the issue outside of AutoCAD and has offered to log a bug. Hopefully it’ll get addressed in a future build of IronRuby (or someone will spell out how I can handle it cleanly from my side).

TrackBack

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

Listed below are links to weblogs that reference Jigging an AutoCAD solid using IronRuby and .NET (well, almost):

blog comments powered by Disqus

Feed/Share

10 Random Posts