October 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 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

  {

    [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

        {

          ScriptEngine engine = Python.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;

    }

  }

}

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 |

      aei.UserInputControls.NoNegativeResponseAccepted)

    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

    else:

      self._end = res.Value

    return aei.SamplerStatus.OK

 

  # The update function

 

  def Update(self):

 

    # Recreate our Solid3d box

 

    try:

      # 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._sol.CreateBox(x,y,z)

      self._sol.TransformBy(

        ag.Matrix3d.Displacement(

          ag.Vector3d(

            self._start.X + x/2,

            self._start.Y + y/2,

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

    except:

      return False

    return True

 

# Create a box using a jig

 

@autocad_command

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

 

    btr.AppendEntity(sol)

    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:

      tr.Commit()

 

    tr.Dispose()

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

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

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

blog comments powered by Disqus

Feed/Share

10 Random Posts