Through the Interface: Jigging an AutoCAD solid using IronPython and .NET

May 2015

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


« Using IronPython with AutoCAD | Main | Free-form modeling in AutoCAD 2010 using .NET »

March 23, 2009

Jigging an AutoCAD solid using IronPython and .NET

After getting my feet wet in the last post with my first IronPython application running inside AutoCAD, I decided it was time to attack a slightly more challenging problem: jigging a database-resident Solid3d object. The idea had come after I’d received a question by email from David Wolfe, who wanted to have a fully rendered 3D view of a cylinder he was jigging.

I’d done something similar for a prototype application I worked on late last year (which was demoed at AU). The jig itself only collected the selection data I needed – the display of the Solid3d objects was handled either via the Transient Graphics subsystem or by modifying database-resident Solid3d objects (which were interconnected by a separate system of relationships and constraints). But anyway, the point is that only when the Solid3d objects were database-resident could I get rendered graphics to be generated for them via either the conceptual or realistic visual styles.

Which is why it occurred to me that the technique shown in this recent post for jigging db-resident blocks with attributes might also apply here, too.

And, just for fun, why not do the whole thing in Python? (Actually, which hindsight I can now think of a lot of reasons… :-)

Part of my rationale behind attempting this was that we were going to have to derive our jig class from EntityJig and make sure the appropriate methods were overridden for AutoCAD to then call our jig at the right moments. This is something I had doubts about being able to do, given my previous experience. IronPython is surprisingly good at allowing these methods to be implemented and called dynamically – something that I expect will grow on me – but the downside was that with the PYLOAD integration we used in the last post it is very hard to tell why things aren’t working. It took me a number of hours to work out that an __init__ function was needed – one which took an Entity and passed it to the constructor of the base class, EntityJig – and that without it the application would simply crash. A tighter integration of Python inside AutoCAD - and, I expect, overall better tooling for IronPython when working with .NET classes - would probably help avoid this kind of thrashing, but with what we have today it was pretty painful.

Before we look at the Python code, here’s an update version of the C# PythonLoader functionality: the only real difference being the try-catch block around the code which hosts our IronPython scripting engine and calls ExecuteFile():

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



    public static void PythonLoadCmdLine()






    public static void PythonLoadUI()





    public static void PythonLoad(bool useCmdLine)


      Document doc =


      Editor ed = doc.Editor;


      short fd =



      // 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 =



      // And then try to load and execute it


      if (pr.Status == PromptStatus.OK)





    public ResultBuffer PythonLoadLISP(ResultBuffer rb)


      const int RTSTR = 5005;


      Document doc =


      Editor ed = doc.Editor;


      if (rb == null)


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




        // 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 =



            (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();



        catch (System.Exception ex)


          Document doc =


          Editor ed = doc.Editor;



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




      return ret;




And here’s the IronPython code for jigging a box inside AutoCAD:

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.EditorInput as aei

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 jig to create a Solid3d - in this case a box


class SolidJig(aei.EntityJig):


  # Initialization function


  def __init__(self, ent):


    # Store the object and call the base class


    self._sol = ent

    aei.EntityJig.__init__(self, ent)


  # The function called to run the jig


  def StartJig(self, ed, pt):


    # The start point is specific outside the jig


    self._start = pt

    self._end = pt

    return ed.Drag(self)


  # The sampler function


  def Sampler(self, prompts):


    # Set up our selection options


    jo = aei.JigPromptPointOptions()

    jo.UserInputControls = (

      aei.UserInputControls.Accept3dCoordinates |

      aei.UserInputControls.NoZeroResponseAccepted |


    jo.Message = "\nSelect end point: "     


    # Get the end point of our box


    res = prompts.AcquirePoint(jo)

    if self._end == res.Value:

      return aei.SamplerStatus.NoChange


      self._end = res.Value

    return aei.SamplerStatus.OK


  # The update function


  def Update(self):


    # Recreate our Solid3d box



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


      x = self._end.X - self._start.X

      y = self._end.Y - self._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






            self._start.X + x/2,

            self._start.Y + y/2,

            self._start.Z + z/2)))


      return False

    return True


# Create a box using a jig



def boxjig():


  doc = aas.Application.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(

      bt[ads.BlockTableRecord.ModelSpace], ads.OpenMode.ForWrite)


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


    sol = ads.Solid3d()

    sol.RecordHistory = True


    # Now we add our solid



    tr.AddNewlyCreatedDBObject(sol, True)


    # And call the jig before finishing


    sj = SolidJig(sol) 

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


    # Only commit if all completed well


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




When we execute the PYLOAD command, load our .py file and execute the BOXJIG command, we’ll be able to jig a Solid3d in a non-wireframe 3D view:

Jigging a box with a conceptual visual style

 Jigging a box with a conceptual visual style 2

Something to note: I haven’t spent time optimizing this code, so there’s a good chance it’s sub-optimal (and may well leak memory). The point was not to demonstrate a definitive approach to solving this particular problem but rather to see whether it was possible to attack a problem of non-trivial complexity with the current toolset and my general lack of knowledge of the Python language.

Well, it’s obviously possible – as I somehow managed to do it – but, to come clean, I did cheat somewhat: I had a C# project open at the same time which I regularly referred to for IntelliSense look-ups. I didn’t do my full development in C# and then convert it to Python, though, so I see this more as a stop-gap to help address some of the current limitations in the tools. That’s the story I’m going with, anyway. :-)


TrackBack URL for this entry:

Listed below are links to weblogs that reference Jigging an AutoCAD solid using IronPython and .NET:

blog comments powered by Disqus


10 Random Posts