I might also have called this post "Overruling AutoCAD 2010's entity display and explode using Boo", as it complements the equivalent posts for C#, F#IronPython and IronRuby, but I felt it appropriate to combine the post with an introduction to what Boo is all about.

Boo logo What is Boo and how did I come to look into it? Knowing of my recent interest in the various scripting technologies being made available for .NET, a colleague at Autodesk recently pointed me at the Boo programming language (and here is the official page for the language, including its various downloads).

First, to get this out the way: Boo is really cool. I can relate to the drivers behind it, which were to create a statically-typed language based on Python that works really well with .NET. And with many of the rough edges smoothed away, especially those related to object-orientation, which tends to feel as though it was bolted on to Python as an afterthought.

As it's statically-typed, Boo provides compile-time code checking and in theory could also provide intellisense and code completion (to which I unfortunately find myself increasingly addicted). It uses type inference – just as F# does – so there's no need for the complex variable declarations we're used to seeing in C#.

I've had fun playing around with dynamically typed languages (such as IronPython and IronRuby, which are based on Microsoft's Dynamic Language Runtime), but for now I still find the benefits brought by static typing to be compelling, especially when you have to launch a relatively heavyweight desktop application to run your code. As far as I can tell much of the productivity benefit of working with dynamic languages in such an environment come from using an integrated REPL – just as AutoLISP has via AutoCAD's command-line – but right now we don't have that kind of capability built into our products for other scripting languages.

While primarily statically-typed, Boo also has dynamic capabilities. For instance, you can very easily make use of duck typing within your code, just as we will get with C# 4.0's dynamic keyword. Duck typing allows you to get away from deriving sub-classes and implementing interfaces: as long as you expose appropriately-named methods from your object, they will get called (for those of you who aren't aware, the term comes from the phrase "If it walks like a duck and quacks like a duck, it must be a duck." :-).

Boo is compiled: it allows you to create executables and class libraries (.DLLs) with custom .NET attributes, which makes it much easier to implement commands inside AutoCAD (we can use the standard NETLOAD command directly, rather than needing to implement something like PYLOAD and RBLOAD). Boo doesn't use the DLR, as it's not really a dynamic language, which means the IL generated is similar to what you'd expect from C# or VB.NET. Take a look using Reflector, if you're interested.

How did I get started with Boo? My first step was to install BooLangStudio, a Boo integration for Visual Studio 2008. I didn't even need to download the Boo language separately, which was nice. The BooLangStudio integration is still in Alpha but generally works pretty well. Aside from missing intellisense I also found that I wasn't able to debug into Class Libraries, but in theory both of these capabilities are altogether doable. Oh, and I had a problem using the CopySourceAsHtml plugin, which I ended up having to rebuild to workaround an issue with the default tab size not being available for .boo source files.

To get the code working, I started by copying the Python code from the post I mentioned earlier and then proceeded to update it for the differences Boo has with Python.

I very much liked the OO- and CLR-related changes:

  • You specifically identify virtual method implementations with the override keyword
    • On the downside I found it important to nail down argument types with the as keyword, which is slightly more cumbersome
      • Unless you use duck typing you will generally need to cast more often, but that seems acceptable
  • The somewhat ugly __init__ methods are now named constructor, instead (yay)
  • The self argument is now implicit (double-yay)

All very logical modifications to the Python language, in my opinion.

Some additional things to call out:

  • Boo doesn't yet support global variables, so these had to be declared as static members of a class
  • There were a few minor issues with enumerations that needed working around (but these were thankfully compile-time errors, so easy to chase down)

Here's the Boo code:

namespace OverruleSample

 

import System

import Autodesk.AutoCAD.Runtime

import Autodesk.AutoCAD.ApplicationServices

import Autodesk.AutoCAD.DatabaseServices

import Autodesk.AutoCAD.EditorInput

import Autodesk.AutoCAD.GraphicsInterface

import Autodesk.AutoCAD.Geometry

import Autodesk.AutoCAD.Colors

 

# Set some global constants & variables

 

class Globals: 

  public static appName = "TTIF_PIPE"

  public static appCode = 1001

  public static radCode = 1040

  public static radius = 0.0

  public static overruling = false

 

class PipeDrawOverrule(DrawableOverrule):

 

  /*

  The base class for our draw overrules specifying the

  registered application name for the XData upon which

  to filter

  */

 

  protected _sweepOpts

 

  def constructor():

 

    /*

    Tell AutoCAD to filter on our application name

    (this means our overrule will only be called

    on objects possessing XData with this name)

    */

 

    cast(DrawableOverrule, self).SetXDataFilter(Globals.appName)

 

class LinePipeDrawOverrule(PipeDrawOverrule):

 

  /*

  An overrule to make a pipe out of a line

  */

 

  public static theOverrule = LinePipeDrawOverrule()

 

  def constructor():

    _sweepOpts = SweepOptions()

    super()

 

  override def WorldDraw(d as Drawable, wd as WorldDraw):

 

    radius = PipeRadiusForObject(d)

 

    if radius > 0.0:

      # Draw the line as is, with overruled attributes

 

      super(d, wd)

 

      ln = d as Line

 

      if not d.Id.IsNull and ln.Length > 0.0:

        # Draw a pipe around the line

 

        c = wd.SubEntityTraits.TrueColor

        wd.SubEntityTraits.TrueColor = EntityColor(0x00AFAFFF)

        wd.SubEntityTraits.LineWeight = LineWeight.LineWeight000

        start = ln.StartPoint

        end = ln.EndPoint

        norm = Vector3d(

          end.X - start.X,

          end.Y - start.Y,

          end.Z - start.Z

        )

        clr = Circle(start, norm, radius)

        pipe = ExtrudedSurface()

        try:

          pipe.CreateExtrudedSurface(clr, norm, _sweepOpts)

        except:

          doc = Application.DocumentManager.MdiActiveDocument

          doc.Editor.WriteMessage(

            "\nFailed with CreateExtrudedSurface."

          )

        clr.Dispose()

        pipe.WorldDraw(wd)

        pipe.Dispose()

        wd.SubEntityTraits.TrueColor = c

      return true

    return super(d, wd)

 

  override def SetAttributes(d as Drawable, t as DrawableTraits):

 

    b = super(d, t)

 

    radius = PipeRadiusForObject(d)

 

    if radius > 0.0:

      # Set color to magenta

      t.Color = 6

      # and lineweight to .40 mm

      t.LineWeight = LineWeight.LineWeight040

 

    return b

 

class CirclePipeDrawOverrule(PipeDrawOverrule):

 

  /*

  An overrule to make a pipe out of a circle

  */

 

  public static theOverrule = CirclePipeDrawOverrule()

 

  def constructor():

    _sweepOpts = SweepOptions()

    super()

 

  override def WorldDraw(d as Drawable, wd as WorldDraw):

 

    radius = PipeRadiusForObject(d)

 

    if radius > 0.0:

      # Draw the circle as is, with overruled attributes

 

      super(d, wd)

 

      cir = d as Circle

 

      # Needed to avoid ill-formed swept surface

 

      if cir.Radius > radius:

        # Draw a pipe around the circle

 

        c = wd.SubEntityTraits.TrueColor

        wd.SubEntityTraits.TrueColor = EntityColor(0x3FFFE0E0)

        wd.SubEntityTraits.LineWeight = LineWeight.LineWeight000

        start = cir.StartPoint

        cen = cir.Center

        norm = Vector3d(

          cen.X - start.X,

          cen.Y - start.Y,

          cen.Z - start.Z

        )

        clr = Circle(start, norm.CrossProduct(cir.Normal), radius)

        pipe = SweptSurface()

        pipe.CreateSweptSurface(clr, d, _sweepOpts)

        clr.Dispose()

        pipe.WorldDraw(wd)

        pipe.Dispose()

        wd.SubEntityTraits.TrueColor = c

      return true

    return super(d, wd)

 

  override def SetAttributes(d as Drawable, t as DrawableTraits):

 

    b = super(d, t)

 

    radius = PipeRadiusForObject(d)

 

    if radius > 0.0:

      # Set color to yellow

      t.Color = 2

      # and lineweight to .60 mm

      t.LineWeight = LineWeight.LineWeight060

 

    return b

 

class LinePipeTransformOverrule(TransformOverrule):

 

  /*

  An overrule to explode a linear pipe into Solid3d objects

  */

 

  public static theOverrule = LinePipeTransformOverrule()

 

  private _sweepOpts

 

  def constructor():

    _sweepOpts = SweepOptions()

 

  override def Explode(e as Entity, objs as DBObjectCollection):

 

    radius = PipeRadiusForObject(e)

 

    if radius > 0.0:

 

      ln = e as Line

      if not e.Id.IsNull and ln.Length > 0.0:

        # Draw a pipe around the line

 

        start = ln.StartPoint

        end = ln.EndPoint

        norm = Vector3d(

          end.X - start.X,

          end.Y - start.Y,

          end.Z - start.Z

        )

        clr = Circle(start, norm, radius)

        pipe = ExtrudedSurface()

        try:

          pipe.CreateExtrudedSurface(clr, norm, _sweepOpts)

        except:

          doc = Application.DocumentManager.MdiActiveDocument

          doc.Editor.WriteMessage(

            "\nFailed with CreateExtrudedSurface."

          )

        clr.Dispose()

        objs.Add(pipe)

      return

    super(e, objs)

 

class CirclePipeTransformOverrule(TransformOverrule):

 

  /*

  An overrule to explode a circular pipe into Solid3d objects

  */

 

  public static theOverrule = CirclePipeTransformOverrule()

 

  private _sweepOpts

 

  def constructor():

    _sweepOpts = SweepOptions()

 

  override def Explode(e as Entity, objs as DBObjectCollection):

 

    radius = PipeRadiusForObject(e)

 

    if radius > 0.0:

      cir = e as Circle   

      if cir.Radius > radius:

 

        start = cir.StartPoint

        cen = cir.Center

        norm = Vector3d(

          cen.X - start.X,

          cen.Y - start.Y,

          cen.Z - start.Z

        )

        clr = Circle(start, norm.CrossProduct(cir.Normal), radius)

        pipe = SweptSurface()

        pipe.CreateSweptSurface(clr, e, _sweepOpts)

        clr.Dispose()

        objs.Add(pipe)

      return

    super(e, objs)

 

def EnableOverrule(enable):

 

  /*

  Regen to see the effect

  (turn on/off Overruling and LWDISPLAY)

  */

 

  Overrule.Overruling = enable

  if enable:

    Application.SetSystemVariable("LWDISPLAY", 1)

  else:

    Application.SetSystemVariable("LWDISPLAY", 0)

 

  doc = Application.DocumentManager.MdiActiveDocument

  doc.SendStringToExecute("REGEN3\n", true, false, false)

  doc.Editor.Regen()

 

class Commands:

 

  [CommandMethod("OVERRULE1")]

  def Overrule1():

 

    # Only add the overrule if not currently attached

 

    if not Globals.overruling:

 

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Line)),

        LinePipeDrawOverrule.theOverrule,

        true

      )

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Line)),

        LinePipeTransformOverrule.theOverrule,

        true

      )

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeDrawOverrule.theOverrule,

        true

      )

      ObjectOverrule.AddOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeTransformOverrule.theOverrule,

        true

    
  )

 

      Globals.overruling = true

      EnableOverrule(true)

 

  [CommandMethod("OVERRULE0")]

  def Overrule0():

 

    # Only remove the overrule if previously added

 

    if Globals.overruling:

 

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Line)),

        LinePipeDrawOverrule.theOverrule

      )

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Line)),

        LinePipeTransformOverrule.theOverrule

      )

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeDrawOverrule.theOverrule

      )

      ObjectOverrule.RemoveOverrule(

        RXClass.GetClass(typeof(Circle)),

        CirclePipeTransformOverrule.theOverrule

      )

 

      Globals.overruling = false

      EnableOverrule(false)

 

  # Should be able to use CommandFlags.UsePickSet instead of 2

  [CommandMethod("MP", cast(CommandFlags,2))]

  def MakePipe():

 

    doc = Application.DocumentManager.MdiActiveDocument

    db = doc.Database

    ed = doc.Editor

 

    # Ask the user to select the entities to make into pipes

 

    pso = PromptSelectionOptions()

    pso.AllowDuplicates = false

    pso.MessageForAdding = "\nSelect objects to turn into pipes: "

 

    selRes = ed.GetSelection(pso)

 

    # If the user didn't make valid selection, we return

    # Should be able to use PromptStatus.OK instead of 5100

 

    if selRes.Status != cast(PromptStatus,5100):

      return

 

    ss = selRes.Value

 

    # Ask the user for the pipe radius to set

 

    pdo = PromptDoubleOptions("\nSpecify pipe radius:")

 

    # Use the previous value, if if already called

 

< span style="line-height: 140%">    if Globals.radius > 0.0:

      pdo.DefaultValue = Globals.radius

      pdo.UseDefaultValue = true

 

    pdo.AllowNegative = false

    pdo.AllowZero = false

 

    pdr = ed.GetDouble(pdo)

 

    # Return if something went wrong

    # Should be able to use PromptStatus.OK instead of 5100

 

    if pdr.Status != cast(PromptStatus,5100):

      return

 

    # Set the "last radius" value for when

    # the command is called next

 

    Globals.radius = pdr.Value

 

    # Use a transaction to edit our various objects

 

    tr = db.TransactionManager.StartTransaction()

 

    # Loop through the selected objects

 

    for o as SelectedObject in ss:

 

      # We could choose only to add XData to the objects

      # we know will use it (Lines and Circles, for now)

 

      obj = tr.GetObject(o.ObjectId, OpenMode.ForWrite)

 

      SetPipeRadiusOnObject(tr, obj, Globals.radius)

 

    tr.Commit()

    tr.Dispose()

 

 def PipeRadiusForObject(obj as DBObject):

 

  /*

  Get the XData for a particular object

  and return the "pipe radius" if it exists

  */

 

  res = 0.0

 

  try:

    rb = obj.XData

    if rb is null:

      return res

 

    foundStart = false

 

    for tv as TypedValue in rb:

      if (tv.TypeCode == Globals.appCode

        and tv.Value == Globals.appName):

        foundStart = true

      else:

        if foundStart:


0;        
if tv.TypeCode == Globals.radCode:

            res = tv.Value

            break

 

    rb.Dispose()

  except:

    return 0.0

  return res

 

def SetPipeRadiusOnObject(

  tr as Transaction, obj as DBObject, radius

):

 

  /*

  Set the pipe radius as XData on a particular object

  */

 

  db = obj.Database

 

  # Make sure the application is registered

  # (we could separate this out to be called

  # only once for a set of operations)

 

  rat as SymbolTable = tr.GetObject(

      db.RegAppTableId, OpenMode.ForRead

    )

 

  if not rat.Has(Globals.appName):

    rat.UpgradeOpen()

    ratr = RegAppTableRecord()

    ratr.Name = Globals.appName

    rat.Add(ratr)

    tr.AddNewlyCreatedDBObject(ratr, true)

 

  # Create the XData and set it on the object

 

  rb = ResultBuffer(

      TypedValue(Globals.appCode, Globals.appName),

      TypedValue(Globals.radCode, Globals.radius)

    )

  obj.XData = rb

  rb.Dispose()

Once the code is built into a Class Library, you should be able to load it via NETLOAD and execute the commands it defines, MP, OVERRULE0 and OVERRULE1.

As for the results of running these commands, they should be identical to what we've seen in the previous posts in this series. No need for yet more identical screenshots… 🙂

I'm sure many of you are rolling your eyes at having yet another programming option presented to you… so far I've been presenting lots of different options, without necessarily telling people what they should use. This is deliberate: my aim is to present the information I've been able to glean on the various language options rather than to pass judgment on them.

All the languages I've presented thus far relate to the use of .NET, whether via the CLR or the DLR. The future may hold different decisions, depending on the product and its platform, but for now here's my thought process when working with AutoCAD and .NET…

I personally expect to use C# and F# for the majority of projects upon which I work (the choice will depend on the problem domain), but I can imagine tackling the occasional task in IronPython – as it's more stable than IronRuby at the time of writing - if I really need something more dynamic. This is likely to be for certain types of problem where dynamic – probably even self-modifying – code makes more sense to me… for the occasional project that is likely to benefit from duck typing I'm more likely to use Boo (or C#, in time). But then I'm still getting my head around the type of problem best suited to dynamic languages: these will, I'm sure, become more obvious, in time. As more of our products support dynamic language capabilities directly this will also play a role in the decision process.

  1. Fernando Malard Avatar
    Fernando Malard

    Hi Kean,

    Really tricky but in fact you will end with several CLI options as people start to migrate or create portings for CLI. I think as more languages you have more confuse dummies will be when they start to coding for AutoCAD.

    I would like to see something more useful CLI such as LISP# or VisualLISP#. Imagine how fantastic this would be for the existing VisualLISP programmers to reuse their huge routines inside AutoCAD.

    Have you heard something about LISP CLI?

    Regards,
    Fernando.

  2. Kean Walmsley Avatar

    Hi Fernando,

    As a dynamic language Lisp is best suited to the DLR and there was, in fact, an IronLisp implementation (inspired in turn by LSharp, which was CLI-based) before the author decided to focus his efforts on IronScheme instead.

    Scheme is a minimalist Lisp dialect that differs significantly from AutoLISP/Visual LISP. Porting any serious AutoLISP application to IronScheme would be a significant effort, and frankly that time would be better spent learning and migrating the code to VB.NET or C# (in my opinion). I would even go so far as saying - bearing in mind this is coming from someone who has never worked with Scheme - that the most valuable knowledge you'd bring from the world of AutoLISP to Scheme is knowledge of how to match up brackets. 🙂 But I could well be wrong...

    AutoLISP is in no danger of disappearing anytime soon from AutoCAD (although there are flavours of AutoCAD that do not support access to their objects from AutoLISP), so my best advice is "leave your existing AutoLISP code where it is, and interface with .NET modules when you need to."

    The opinions I've expressed around language choice in this post are future-focused, not taking into account many other factors (not least of which are current skillset and the possible existence of a legacy codebase).

    As you no doubt remember, we've been here before and will no doubt be here again as the language landscape shifts and evolves.

    Regards,

    Kean

  3. Thomas Lessiak Avatar
    Thomas Lessiak

    Hello together,

    Is it really a new way for all those posts to find a way with all that script languages to handling Acad. I think most of users are interested in practical Ideas for solving the daily aproaches in programmers work in a common way. Im a indoor developer, not interested in finding a way for the same problems with different scripts. I will hope to find supporting elements of real world questions without joining the Autodesk Developer networks, we dont have 1000$ to get Information of new things. We are all in small firms drafting drawings with supporting ourselfs with VBA and .NET Development. Now, VBA will find an End and .NET is now our toolkit to accelarte our work. Please shoe us solutions! Thank you

  4. Kean Walmsley Avatar

    Hi Thomas,

    Thank you for expressing your opinion.

    I'm sure there are people out there who agree with you, just as I'm hopeful there are people out there that find this post interesting (and perhaps one day may even find it useful).

    I do my best to post on a variety of topics, some of which are of interest to a broad audience, some of which are of more interest to a smaller subset of my readership. And there are definitely quite substantial periods during which my posts are about things that interest me, personally, without being of everyday relevance for most.

    If you have specific topics you feel are relevant to be addressed, feel free to post a comment or send me an email. But I reserve the right to continue to choose the topics upon which I post and while I hope some of them may interest you, others will almost certainly not.

    Regards,

    Kean

  5. Andrew Buchan Avatar

    What a happy coincidence, I was just beginning to wonder if anyone had used Boo for AutoCAD.

    I now use Boo for all my stand-alone applications, as the syntax is so nice. The most viable tool for it is the open-source Sharpdevelop IDE, which is a lightweight alternative to Visual Studio. It comes with a Boo (and IronPython) plugin. It can be a bit buggy, but I believe it is the best option out there at time of writting.

    I use lisp for just about all my AutoCAD developments, and prefer it over VBA for the ease and speed with which you can do things, especially since you can use Visual Lisp calls to get to ActiveX, and thanks to OpenDCL, rich dialog boxes are a breeze.

    Having said that, my main in-house AutoCAD project spans just under 14,000 lines of lisp code (excluding the third-party libs..), and there are many times at which I wished I had things like object-orientation and namespaces.

    I personally doubt whether .Net languages will fully replace lisp due to the aforementioned ease and speed factors, even if its harder to manage larger projects. I haven't fully looked into the above code snippet, but it seems a lot for what it does, and it would be good to see a lisp listing for comparison, just for size.

    One avenue which would be interesting to pursue is Boo's ability to be expanded and it's suitability for building Domain Specific Languages.
    Perhaps we could build a new and improved AutoLisp, with custom objects...

    Any takers?

  6. Kean Walmsley Avatar

    Interesting - thanks, Andrew.

    The Overrule API just isn't available for use by AutoLISP, which would make it complicated to make a comparison. 🙂

    I'm still debating whether to spend some time looking into IronScheme, just for the sake of completeness.

    I've thought about the potential for a DSL for a product like AutoCAD... in a sense we have a very rudimentary DSL in our Script files, but perhaps something that fits between a general purpose language and a command-scripting language would make sense. My overally feel is that the development problems AutoCAD developers need to face on a day-to-day basis are varied enough that streamlining the coding experience via a DSL would end up just hampering people's ability to solve more generic programming tasks. But that's just my impression - I need to chew on this a little more.

    Kean

Leave a Reply to Andrew Buchan Cancel reply

Your email address will not be published. Required fields are marked *