« October 2007 | Main | December 2007 »

Leaving Las Vegas

I'm sat here at McCarran airport, tapping in a quick post before flying off to Boston. I didn't get the chance to post on the happenings this week at AU (or about anything much else, for that matter), but many others have, so I expect that's OK.

I have to say I really enjoyed this year’s AU, even if I’m now feeling a little frazzled. It was a real pleasure to put so many faces to names over the course of the week, as well as to catch up with old acquaintances.

I'd also like to say a quick (but big) "thank you" to all the presenters and attendees who made the Developer Track at this year's Autodesk University such a success. Early feedback indicates the sessions were generally very well received, and I know my team certainly enjoyed the opportunity to present and to engage in so many interesting discussions. I fully expect us to run a Developer Track of a similar (or greater) scale at AU 2008, with appropriate tweaks/enhancements based on the feedback we receive.

If any of you have input on what we might improve for next year, please post a comment or send me an email.

Thank you very much (said in my best Elvis voice, of course).

November 30, 2007 in Training | Permalink | Comments (1) | TrackBack

Metaprogramming with AutoCAD - Part 3

In parts 1 & 2 of this series we looked at metaprogramming with AutoCAD using AutoLISP and VB(A), and then using VB.NET and C#.

In this post we're going to look at what's possible from F#, through the lens of my relative inexperience with the language, of course.

The quotations mechanism in F# appears to be the way to represent, analyse and execute program structure. This article describes the concepts, although it's quite deep and doesn't address the case that's most immediately interesting to AutoCAD develeopers: the ability to evaluate and execute code represented as a string. In fact, this doesn't yet appear to be part of the F# language, according to this thread, which helped me formulate the below F# version of the code I showed in my last post.

I would like to see a Microsoft.FSharp.FSharpCodeProvider class allowing execution of F# code provided as a string. I don't know whether the plan is to provide this - or something like it - but I can certainly see it being of use.

What we can do today is execute C# and VB.NET code, just as we did in the previous post, but this time the generation language is F#. The C# and VB.NET code we create is heterogeneous from the perspective of F#, of course.

Here's the F# code:

#light


module MyNamespace.MyApplication


// Import managed assemblies


#I @"C:\Program Files\Autodesk\AutoCAD 2008"


#r "acdbmgd.dll"

#r "acmgd.dll"


open System

open System.CodeDom.Compiler

open System.Reflection;

open Microsoft.VisualBasic;

open Microsoft.CSharp;

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.DatabaseServices


let acadFolder =

  "c:\\Program Files\\Autodesk\\AutoCAD 2008\\"


let CompileVBExpression (expression:string) =

    let source =

        "Imports System\n" ^

        "Imports Autodesk.AutoCAD.Runtime\n" ^

        "Imports Autodesk.AutoCAD.ApplicationServices\n" ^

        "Imports Autodesk.AutoCAD.DatabaseServices\n" ^

        "Imports Autodesk.AutoCAD.EditorInput\n" ^

        "Imports Autodesk.AutoCAD.Geometry\n" ^

        "Namespace VBCodeEval\n" ^

        "Public Class VBCodeEval\n" ^

        "Public Function EvalCode() As Object\n" ^

        "Return " ^ expression ^ "\n" ^

        "End Function\n" ^

        "End Class\n" ^

        "End Namespace"


    let provider = new VBCodeProvider()

    let parameters = new CompilerParameters()

    ignore

      (parameters.ReferencedAssemblies.Add

        ("system.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acdbmgd.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acmgd.dll"))

    parameters.CompilerOptions <- "/t:library"

    parameters.GenerateInMemory <- true

    provider.CompileAssemblyFromSource(parameters, [|source|])


let RunVBCode (expression:string) =

    let results = CompileVBExpression expression

    if not results.Errors.HasErrors then

        let a = results.CompiledAssembly

        let o = a.CreateInstance("VBCodeEval.VBCodeEval")

        let t = o.GetType()

        let mi = t.GetMethod("EvalCode")

        mi.Invoke(o, null)

    else

        null


let CompileCSExpression (expression:string) =

    let source =

        "using System;\n" ^

        "using Autodesk.AutoCAD.Runtime;\n" ^

        "using Autodesk.AutoCAD.ApplicationServices;\n" ^

        "using Autodesk.AutoCAD.DatabaseServices;\n" ^

        "using Autodesk.AutoCAD.EditorInput;\n" ^

        "using Autodesk.AutoCAD.Geometry;\n" ^

        "namespace CSCodeEval{\n" ^

        "public class CSCodeEval{\n" ^

        "public object EvalCode(){\n" ^

        "return " ^ expression ^ ";\n" ^

        "}\n" ^

        "}\n" ^

        "}"


    let provider = new CSharpCodeProvider();

    let parameters = new CompilerParameters();

    ignore

      (parameters.ReferencedAssemblies.Add

        ("system.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acdbmgd.dll"))

    ignore

      (parameters.ReferencedAssemblies.Add

        (acadFolder ^ "acmgd.dll"))

    parameters.CompilerOptions <- "/t:library"

    parameters.GenerateInMemory <- true

    provider.CompileAssemblyFromSource(parameters, [|source|])


let RunCSCode (expression:string) =

    let results = CompileCSExpression expression

    if not results.Errors.HasErrors then

        let a = results.CompiledAssembly

        let o = a.CreateInstance("CSCodeEval.CSCodeEval")

        let t = o.GetType()

        let mi = t.GetMethod("EvalCode")

        mi.Invoke(o, null)

    else

        null


[<CommandMethod("ev")>]

let evaluate () =


  let csCode =

    "typeof(Autodesk.AutoCAD." ^

    "ApplicationServices.Application)"


  let vbCode =

    "GetType(Autodesk.AutoCAD." ^

    "ApplicationServices.Application)"


  let ed =

    Autodesk.AutoCAD.ApplicationServices.

      Application.DocumentManager.

        MdiActiveDocument.Editor;


  ed.WriteMessage("\nEvaluating C# code:\n" ^ csCode);


  let csRes = RunCSCode csCode


  if csRes != null then

    ed.WriteMessage("\nC# code returned: " ^ csRes.ToString());


  ed.WriteMessage("\nEvaluating VB code:\n" ^ vbCode);


  let vbRes = RunVBCode vbCode


  if vbRes != null then

    ed.WriteMessage("\nVB code returned: " ^ vbRes.ToString()); 

And when we run the "ev" command, we see the same results as with the VB.NET and C# implementation in the last post:

Command: ev

Evaluating C# code:

typeof(Autodesk.AutoCAD.ApplicationServices.Application)

C# code returned: Autodesk.AutoCAD.ApplicationServices.Application

Evaluating VB code:

GetType(Autodesk.AutoCAD.ApplicationServices.Application)

VB code returned: Autodesk.AutoCAD.ApplicationServices.Application

November 26, 2007 in AutoCAD, AutoCAD .NET, F#, Visual Studio | Permalink | Comments (1) | TrackBack

Metaprogramming with AutoCAD - Part 2

In this post we're going to continue the topic started in Part 1 of this series, which looked briefly at metaprogramming with AutoCAD using AutoLISP and VB(A). Now we're going to look at .NET, focusing initially on C# and VB.NET.

[I found the inspiration for the code in this post from The Code Project, although I had to update the code to use non-deprecated CLR methods as well as making it work for AutoCAD, of course.]

While .NET doesn't provide something as simple as an Eval() function, it actually provides something much more interesting. The CLR exposes the ability to compile and execute source code in .NET languages for which implementations of the CodeDomProvider protocol have been provided.

The Microsoft.CSharp namespace, for instance, contains the CSharpCodeProvider class, which allows you to specify and compile C# code from any .NET language. Microsoft.VisualBasic contains VBCodeProvider, which does the same for VB.NET.

Which means that it's actually very easy to implement dynamic metaprogramming in a homogeneous or heterogeneous fashion from .NET. Yay! :-)

As I had some time, and decided that implementing this for both C# and VB.NET would tell the story nicely, I've provided code below for both environments.

Here's the C# code, which shows how to compile and execute C# (homogeneous) and VB.NET (heterogeneous) code:

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Microsoft.CSharp;

using Microsoft.VisualBasic;

using System.CodeDom.Compiler;

using System.Reflection;

using System.Text;

using System;


namespace Metaprogramming

{

  public class Commands

  {

    const string acadFolder =

      "c:\\Program Files\\Autodesk\\AutoCAD 2008\\";


    // EvalCS: Evaluates C# source


    public static object EvalCS(string csCode)

    {

      DocumentCollection dm =

          Application.DocumentManager;

      Editor ed = dm.MdiActiveDocument.Editor;


      CSharpCodeProvider cs = new CSharpCodeProvider();

      CompilerParameters cp = new CompilerParameters();


      cp.ReferencedAssemblies.Add("system.dll");

      cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll");

      cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll");


      cp.CompilerOptions = "/t:library";

      cp.GenerateInMemory = true;


      StringBuilder sb = new StringBuilder();

      sb.Append("using System;\n");

      sb.Append("using Autodesk.AutoCAD.Runtime;\n");

      sb.Append(

        "using Autodesk.AutoCAD.ApplicationServices;\n"

      );

      sb.Append("using Autodesk.AutoCAD.DatabaseServices;\n");

      sb.Append("using Autodesk.AutoCAD.EditorInput;\n");

      sb.Append("using Autodesk.AutoCAD.Geometry;\n");


      sb.Append("namespace CSCodeEval{\n");

      sb.Append("public class CSCodeEval{\n");

      sb.Append("public object EvalCode(){\n");

      sb.Append("return " + csCode + ";\n");

      sb.Append("}\n");

      sb.Append("}\n");

      sb.Append("}\n");


      CompilerResults cr =

        cs.CompileAssemblyFromSource(cp, sb.ToString());

      if (cr.Errors.Count > 0)

      {

        ed.WriteMessage(

          "\nErrors evaluating C# code (" +

          cr.Errors.Count +

          "):"

        );

        for (int i = 0; i < cr.Errors.Count; i++)

        {

          ed.WriteMessage(

            "\nLine number " +

            cr.Errors[i].Line + ": " +

            cr.Errors[i].ErrorText

          );

        }

        return null;

      }


      System.Reflection.Assembly a =

        cr.CompiledAssembly;

      object o =

        a.CreateInstance("CSCodeEval.CSCodeEval");


      Type t = o.GetType();

      MethodInfo mi = t.GetMethod("EvalCode");


      object s = mi.Invoke(o, null);

      return s;

    }


    // EvalVB: Evaluates VB source


    public static object EvalVB(string vbCode)

    {

      DocumentCollection dm =

          Application.DocumentManager;

      Editor ed = dm.MdiActiveDocument.Editor;


      VBCodeProvider vb = new VBCodeProvider();

      CompilerParameters cp = new CompilerParameters();


      cp.ReferencedAssemblies.Add("system.dll");

      cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll");

      cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll");


      cp.CompilerOptions = "/t:library";

      cp.GenerateInMemory = true;


      StringBuilder sb = new StringBuilder();

      sb.Append("Imports System\n");

      sb.Append("Imports Autodesk.AutoCAD.Runtime\n");

      sb.Append(

        "Imports Autodesk.AutoCAD.ApplicationServices\n"

      );

      sb.Append(

        "Imports Autodesk.AutoCAD.DatabaseServices\n"

      );

      sb.Append("Imports Autodesk.AutoCAD.EditorInput\n");

      sb.Append("Imports Autodesk.AutoCAD.Geometry\n");


      sb.Append("Namespace VBCodeEval\n");

      sb.Append("Public Class VBCodeEval\n");

      sb.Append("Public Function EvalCode() As Object\n");

      sb.Append("Return " + vbCode + " \n");

      sb.Append("End Function\n");

      sb.Append("End Class\n");

      sb.Append("End Namespace\n");


      CompilerResults cr =

        vb.CompileAssemblyFromSource(cp, sb.ToString());

      if (cr.Errors.Count > 0)

      {

        ed.WriteMessage(

          "\nErrors evaluating VB code (" +

          cr.Errors.Count +

          "):"

        );

        for (int i = 0; i < cr.Errors.Count; i++)

        {

          ed.WriteMessage(

            "\nLine number " +

            cr.Errors[i].Line + ": " +

            cr.Errors[i].ErrorText

          );

        }

        return null;

      }


      System.Reflection.Assembly a =

        cr.CompiledAssembly;

      object o =

        a.CreateInstance("VBCodeEval.VBCodeEval");


      Type t = o.GetType();

      MethodInfo mi = t.GetMethod("EvalCode");


      object s = mi.Invoke(o, null);

      return s;

    }


    [CommandMethod("EV")]

    public void Eval()

    {

      DocumentCollection dm =

          Application.DocumentManager;

      Editor ed = dm.MdiActiveDocument.Editor;


      const string csCode =

        "typeof(Autodesk.AutoCAD." +

        "ApplicationServices.Application)";


      const string vbCode =

        "GetType(Autodesk.AutoCAD." +

        "ApplicationServices.Application)";


      ed.WriteMessage("\nEvaluating C# code:\n" + csCode);


      object result = EvalCS(csCode);


      if (result != null)

        ed.WriteMessage(

          "\nC# code returned: " +

          result.ToString()

        );


      ed.WriteMessage("\nEvaluating VB code:\n" + vbCode);


      result = EvalVB(vbCode);


      if (result != null)

        ed.WriteMessage(

          "\nVB code returned: " +

          result.ToString()

        );

    }

  }

}

Here's the VB.NET code, which shows how to compile and execute C# (heterogeneous) and VB.NET (homogeneous) code:

Imports Autodesk.AutoCAD.Runtime

Imports Autodesk.AutoCAD.ApplicationServices

Imports Autodesk.AutoCAD.EditorInput

Imports Microsoft.CSharp

Imports Microsoft.VisualBasic

Imports System.CodeDom.Compiler

Imports System.Reflection

Imports System.Text

Imports System


Namespace Metaprogramming


  Public Class Commands


    Private Const acadFolder As String = _

      "c:\\Program Files\\Autodesk\\AutoCAD 2008\\"


    'EvalCS: Evaluates C# source


    Public Shared Function EvalCS(ByVal csCode As String) _

    As Object


      Dim dm As DocumentCollection = _

        Application.DocumentManager

      Dim ed As Editor = dm.MdiActiveDocument.Editor


      Dim cs As CSharpCodeProvider = New CSharpCodeProvider

      Dim cp As CompilerParameters = New CompilerParameters

      cp.ReferencedAssemblies.Add("system.dll")

      cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll")

      cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll")

      cp.CompilerOptions = "/t:library"

      cp.GenerateInMemory = True


      Dim sb As StringBuilder = New StringBuilder

      sb.Append("using System;" & vbLf)

      sb.Append("using Autodesk.AutoCAD.Runtime;" & vbLf)

      sb.Append( _

        "using Autodesk.AutoCAD.ApplicationServices;" & vbLf)

      sb.Append( _

        "using Autodesk.AutoCAD.DatabaseServices;" & vbLf)

      sb.Append("using Autodesk.AutoCAD.EditorInput;" & vbLf)

      sb.Append("using Autodesk.AutoCAD.Geometry;" & vbLf)

      sb.Append("namespace CSCodeEval{" & vbLf)

      sb.Append("public class CSCodeEval{" & vbLf)

      sb.Append("public object EvalCode(){" & vbLf)

      sb.Append("return " + csCode + ";" & vbLf)

      sb.Append("}" & vbLf)

      sb.Append("}" & vbLf)

      sb.Append("}" & vbLf)


      Dim cr As CompilerResults = _

        cs.CompileAssemblyFromSource(cp, sb.ToString)


      If (cr.Errors.Count > 0) Then


        ed.WriteMessage( _

          vbLf & "Errors evaluating C# code (" + _

          cr.Errors.Count.ToString + "):")


        Dim i As Integer


        For i = 0 To cr.Errors.Count - 1

          ed.WriteMessage( _

            vbLf & "Line number " + _

            cr.Errors(i).Line.ToString + ": " + _

            cr.Errors(i).ErrorText)

        Next


        Return Nothing


      End If


      Dim a As System.Reflection.Assembly = _

        cr.CompiledAssembly

      Dim o As Object = _

        a.CreateInstance("CSCodeEval.CSCodeEval")

      Dim t As Type = o.GetType

      Dim mi As MethodInfo = t.GetMethod("EvalCode")

      Dim s As Object = mi.Invoke(o, Nothing)


      Return s


    End Function


    'EvalVB: Evaluates VB source


    Public Shared Function EvalVB(ByVal vbCode As String) _

    As Object


      Dim dm As DocumentCollection = _

        Application.DocumentManager

      Dim ed As Editor = dm.MdiActiveDocument.Editor


      Dim vb As VBCodeProvider = New VBCodeProvider

      Dim cp As CompilerParameters = New CompilerParameters

      cp.ReferencedAssemblies.Add("system.dll")

      cp.ReferencedAssemblies.Add(acadFolder + "acdbmgd.dll")

      cp.ReferencedAssemblies.Add(acadFolder + "acmgd.dll")

      cp.CompilerOptions = "/t:library"

      cp.GenerateInMemory = True


      Dim sb As StringBuilder = New StringBuilder

      sb.Append("Imports System" & vbLf)

      sb.Append("Imports Autodesk.AutoCAD.Runtime" & vbLf)

      sb.Append( _

        "Imports Autodesk.AutoCAD.ApplicationServices" & vbLf)

      sb.Append( _

        "Imports Autodesk.AutoCAD.DatabaseServices" & vbLf)

      sb.Append("Imports Autodesk.AutoCAD.EditorInput" & vbLf)

      sb.Append("Imports Autodesk.AutoCAD.Geometry" & vbLf)

      sb.Append("Namespace VBCodeEval" & vbLf)

      sb.Append("Public Class VBCodeEval" & vbLf)

      sb.Append("Public Function EvalCode() As Object" & vbLf)

      sb.Append("Return " + vbCode + " " & vbLf)

      sb.Append("End Function" & vbLf)

      sb.Append("End Class" & vbLf)

      sb.Append("End Namespace" & vbLf)


      Dim cr As CompilerResults = _

        vb.CompileAssemblyFromSource(cp, sb.ToString)


      If (cr.Errors.Count > 0) Then


        ed.WriteMessage( _

          vbLf & "Errors evaluating VB code (" + _

          cr.Errors.Count.ToString + "):")


        Dim i As Integer

        For i = 0 To cr.Errors.Count - 1

          ed.WriteMessage( _

            vbLf & "Line number " + _

            cr.Errors(i).Line.ToString + ": " + _

            cr.Errors(i).ErrorText)

        Next


        Return Nothing


      End If


      Dim a As System.Reflection.Assembly = _

        cr.CompiledAssembly

      Dim o As Object = _

        a.CreateInstance("VBCodeEval.VBCodeEval")

      Dim t As Type = o.GetType

      Dim mi As MethodInfo = t.GetMethod("EvalCode")

      Dim s As Object = mi.Invoke(o, Nothing)

      Return s


    End Function


    <CommandMethod("EV")> _

    Public Sub Eval()


      Dim dm As DocumentCollection = _

        Application.DocumentManager

      Dim ed As Editor = dm.MdiActiveDocument.Editor


      Const csCode As String = _

        "typeof(Autodesk.AutoCAD." + _

        "ApplicationServices.Application)"


      Const vbCode As String = _

        "GetType(Autodesk.AutoCAD." + _

        "ApplicationServices.Application)"


      ed.WriteMessage( _

        vbLf + "Evaluating C# code:" + _

        vbLf + csCode)


      Dim result As Object = EvalCS(csCode)


      If (Not result Is Nothing) Then

        ed.WriteMessage( _

          vbLf + "C# code returned: " + _

          result.ToString)

      End If


      ed.WriteMessage( _

        vbLf + "Evaluating VB code:" + _

        vbLf + vbCode)


      result = EvalVB(vbCode)


      If (Not result Is Nothing) Then

        ed.WriteMessage( _

          vbLf + "VB code returned: " + _

          result.ToString)

      End If


    End Sub


  End Class


End Namespace

When we run the "ev" command, implemented by either of the above code fragments, we see these results:

Command: ev

Evaluating C# code:

typeof(Autodesk.AutoCAD.ApplicationServices.Application)

C# code returned: Autodesk.AutoCAD.ApplicationServices.Application

Evaluating VB code:

GetType(Autodesk.AutoCAD.ApplicationServices.Application)

VB code returned: Autodesk.AutoCAD.ApplicationServices.Application

November 22, 2007 in AutoCAD, AutoCAD .NET, Visual Studio | Permalink | Comments (5) | TrackBack

Visual Studio 2008 & .NET Framework 3.5 released

Microsoft has released Visual Studio 2008 and the .NET Framework 3.5. For the announcement see Soma's blog and for more detailed information see Scott Guthrie's.

I haven't yet worked with this version of Visual Studio, but I'm told it installs side-by-side with previous versions of Visual Studio, so I'll be taking a look after AU.

Some of the areas I'm especially interested in - and this is not an exhaustive list of all the cool stuff the release includes... :-)

  • C#/VB language features, many of which have come from the world of functional programming
    • Automatic properties, extension methods, lambda expressions, object & list initializers, etc. etc.
  • Enhanced HTML/CSS editing
    • I still hand-hack a fair amount of HTML
  • LINQ - language integrated query
    • Allows you to create/manage data(-base) queries via code rather than SQL
  • Integrated browsing of the .NET Framework source

As for AutoCAD development: there should not be a problem writing .NET applications for AutoCAD with Visual Studio 2008 - especially as it allows you to build applications for prior versions of the .NET Framework - but you should not attempt to build ObjectARX applications. The supported C++ compiler for ObjectARX in AutoCAD 2007 & 2008 is Visual Studio 2005.

I'll be back on the subject of metaprogramming in my next post...

November 20, 2007 in Visual Studio | Permalink | Comments (4) | TrackBack

Metaprogramming with AutoCAD - Part 1

A recent comment on one of my F# articles got me thinking about this topic (thanks, Thomas! :-), so I thought I’d write a few posts on it. Next week is AU, and the week after that I’m attending a training class in Boston, so posts may be a little sparse over the coming weeks.

Metaprogramming  – according to the definition on Wikipedia – is the act of writing code that writes or manipulates other programs (or itself). But what is it really all about? The vast majority of programmers are actually metaprogramming without realizing it has such a fancy name.

To help understand metaprogramming, we’re going to focus on two ways of categorizing the various types of metaprogramming activity. Metaprogramming is usually either static or dynamic and homogeneous or heterogeneous (there are other classifications, but we’re not going to worry about those in this article).

  • Static = at compile-time
  • Dynamic = at runtime
  • Homogeneous = the same output language is used as on the input
  • Heterogeneous = a different output language is used to the input language

The most obvious form of metaprogramming is to create machine-code using a compiler (or even an interpreter) for a high-level language. This is a static, heterogeneous act of metaprogramming (although using an interpreter would presumably make this dynamic). Here are a few more interesting examples of metaprogramming I’ve used myself:

  • C++ templates or pre-processor macros to generate lower-level code at compile-time
    • Static (compile-time) and heterogeneous (the generation language is different from the output language)
  • Generation of a series of LISP expressions that are evaluated at runtime
    • Dynamic (runtime) and homogeneous (the generation and output languages are the same)
  • Programmatic creation (perhaps using LISP, C#, VB(A) or C++) of an AutoCAD script that is then executed
    • Dynamic and heterogeneous
  • Composing a SQL statement on-the-fly and using it to query a database
    • Dynamic and heterogeneous

The focus of this series of posts is on dynamic metaprogramming, which allows the modification of code at runtime (rather than compile-time). A further, somewhat more complex, example of dynamic metaprogramming is to redefine functions at runtime (something that’s possible with LISP, for instance), which allows applications to evolve, such as when developing expert systems that “learn” over time.

LISP was really one of the early programming environments that enabled metaprogramming, primarily through its ability to evaluate expression using (eval) and to redefine functions at runtime using (defun). This has been immensely valuable to AutoLISP programmers over the years. When AutoLISP was first introduced it was purely an interpreted language, so dynamic metaprogramming was provided pretty much automatically. In order for metaprogramming to work after the introduction of Visual LISP (which is fundamentally a compiled environment, albeit to an intermediate language), a runtime component supporting dynamic compilation was needed and provided. Very little change was needed in AutoLISP code, although in some rare cases (defun-q) now needs to be used, if it’s important to provide access to the internal representation of functions.

We’ll see this is a common thread for dynamic metaprogramming: by definition you either need to be working in an interpreted environment or will need to have a runtime component available that supports some kind of compilation (probably JIT). Visual LISP provides this, as does VBA and .NET (via the CLR).

Back to AutoLISP: one very common activity is to interpret a string using (read) and then call (eval) on it. The string may have been stored in a drawing, a text file, an external database, or generated on-the-fly. For example:

Command: (eval (read "(* 5 (getvar \"ZOOMFACTOR\"))"))

300

VBA also has native support for dynamic metaprogramming via the Eval() function:

Eval "MsgBox ThisDrawing.Name"

VB6 doesn't have direct support for Eval(), but it seems you can make use of it either by embedding a Script Control or by calling across to the VBA runtime (Googling "VB6 Eval" returned a number of options). I don't know whether it's possible to evaluate and make use of AutoCAD-specific variables - such as ThisDrawing - when using these techniques, however.

Metaprogramming with .NET is not quite so automatic, but is altogether possible, as I’ll show in my next post.

November 19, 2007 in AutoCAD, AutoCAD .NET, Visual Basic & VBA, Visual LISP | Permalink | Comments (6) | TrackBack

Getting the total volume of 3D solids in an AutoCAD model using F#

In one of my sessions at this year's AU, "There's More to .DWG Than AutoCAD®", I'll be showing some VB.NET code that goes through and collects information about solids, presenting it in a dialog along with the sum of the various volumes. You can get the code and the results from Part 1 of the session's handout.

Just for fun, I thought I'd write some F# code to add the volumes of the 3D solid objects in the modelspace of the current drawing. I adopt a similar approach to the VB code - not caring about intersecting volumes, for instance - but obviously the code looks quite different.

I won't step through the code line-by-line, as the last post introduced the fundamental concepts that also apply here.

// Use lightweight F# syntax


#light


(* Declare a specific namespace

  and module name

*)


module MyNamespace.MyApplication


// Import managed assemblies


#I @"C:\Program Files\Autodesk\AutoCAD 2008"


#r "acdbmgd.dll"

#r "acmgd.dll"


open System

open System.Collections.Generic

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.DatabaseServices


// Now we declare our command


[<CommandMethod("volume")>]

let listWords () =


  // Let's get the usual helpful AutoCAD objects


  let doc =

    Application.DocumentManager.MdiActiveDocument;

  let ed = doc.Editor;

  let db = doc.Database;


  // "use" has the same effect as "using" in C#


  use tr =

    db.TransactionManager.StartTransaction();


  // Get appropriately-typed BlockTable and BTRs


  let bt =

    tr.GetObject

      (db.BlockTableId,OpenMode.ForRead)

    :?> BlockTable

  let ms =

    tr.GetObject

      (bt.[BlockTableRecord.ModelSpace],

      OpenMode.ForRead)

    :?> BlockTableRecord


  // A function that accepts an ObjectId and returns

  // the volume of a 3D solid, if it happens to be one


  // Note the valid use of tr, as it is in scope


  let getVolume (x : ObjectId) =

    let obj = tr.GetObject(x,OpenMode.ForRead);

    match obj with

    | :? Solid3d -> (obj :?> Solid3d).MassProperties.Volume

    | _ -> 0.0


  // Use fold_left in a partial application to find

  // the sum of the contents of a list


  let sum =

    List.fold_left (fun x y -> x+y) 0.0


  // And here's where we plug everything together...


  let vol =

    Seq.untyped_to_list ms |> List.map getVolume |> sum


  ed.WriteMessage("\nTotal volume: " + vol.ToString());


  // As usual, committing is cheaper than aborting


  tr.Commit()

The only tricky thing is the use of fold_left to apply an anonymous (or lambda, for the LISPers out there) addition function across the contents of the list containing the individual volumes of the objects in the modelspace.

Here's what we see when we run the "volume" command:

Command: volume

Total volume: 15275.8711619534

This is the same result as displayed by the previous example (although presented with a few more decimal places).

November 16, 2007 in AutoCAD, AutoCAD .NET, F# | Permalink | Comments (1) | TrackBack

More fun with F# and AutoCAD: string extraction and manipulation

I've been working through some draft chapters of Don Syme's Expert F# book (posted here, while the final version will be available in hardcover from early December). I'm definitely enjoying working with F#: the beauty of functional programming combined with the flexibility of .NET is a killer combination.

Before I dive into the sample I put together for today's post, I thought I'd scribble down some musings on the language, to help position the technology in comparison with more popular imperative/object-oriented languages...

Functional programming is great for deep mathematical problems, and so will play well with developers needing to perform complex scientific calculations (for example in the fields of analysis and simulation - domains that are increasing converging with and integrating into design). As I showed in this previous post, you can very easily represent - and even display - complicated scientific functions using this a functional language such as F#.

The other aspect of great interest to me is how this fits with the increasing need to harness multi-processor & multi-core environments: another of my fields of study (many moons ago) was parallel computing, especially using occam 2 to program transputers. That was great fun, but as a programmer working in this type of environment you end up spending a lot of time deciding which tasks are to be executed in parallel. What makes functional programming interesting with respect to concurrency - and this is clearly a major driver behind Microsoft's interest in developing languages in this area - is the ability to harness the power of multiple processing cores to run, in parallel, code that adopts the purely functional paradigm. "Pure" functional code does not create "side-effects", such as when maintaining internal program state, which makes it perfect for distributing across multiple processors/cores. It's this ability to automatically farm out computing operations across your processing resources that is interesting: the manual way is simply too laborious to scale to real-world needs.

While F# is not a "pure" language (and frankly this lack of purity is what makes F# most interesting for AutoCAD programming, as it gives us the freedom to do fun things inside AutoCAD using F#), it is no doubt possible to enforce higher levels of purity in sections of code that can then make use of the increasingly parallel capabilities of modern processors. At least that's what I would expect. :-)

OK, so that's a look at the "high end" of functional programming. I also see a great deal of relevance for this type of technology at the "low end", where you simply want to throw together some quick code to do something simple. And that's the case I'm presenting today.

As I've mentioned before, functional programming can be incredibly succinct. Here's some code F# that goes through the modelspace and paperspace of the current drawing inside AutoCAD, and prints a list of all the distinct words used inside the various MText objects:

// Use lightweight F# syntax


#light


(* Declare a specific namespace

  and module name

*)


module MyNamespace.MyApplication


// Import managed assemblies


#I @"C:\Program Files\Autodesk\AutoCAD 2008"


#r "acdbmgd.dll"

#r "acmgd.dll"


open System

open System.Collections.Generic

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.DatabaseServices


// Now we declare our command


[<CommandMethod("Words")>]

let listWords () =


  // Let's get the usual helpful AutoCAD objects


  let doc =

    Application.DocumentManager.MdiActiveDocument;

  let ed = doc.Editor;

  let db = doc.Database;


  // "use" has the same effect as "using" in C#


  use tr =

    db.TransactionManager.StartTransaction();


  // Get appropriately-typed BlockTable and BTRs


  let bt =

    tr.GetObject

      (db.BlockTableId,OpenMode.ForRead)

    :?> BlockTable

  let ms =

    tr.GetObject

      (bt.[BlockTableRecord.ModelSpace],

      OpenMode.ForRead)

    :?> BlockTableRecord

  let ps =

    tr.GetObject

      (bt.[BlockTableRecord.PaperSpace],

      OpenMode.ForRead)

    :?> BlockTableRecord


  // Now the fun starts...


  // A function that accepts an ObjectId and returns

  // a list of the text contents, or an empty list.


  // Note the valid use of tr, as it is in scope


  let extractText (x : ObjectId) =

    let obj = tr.GetObject(x,OpenMode.ForRead);

    match obj with

    | :? MText -> [(obj :?> MText).Contents];

    | _ -> []


  // A recursive function to print the contents of a list


  let rec printList x =

    match x with

    | h :: t -> ed.WriteMessage("\n" + h); printList t

    | [] -> ed.WriteMessage("\n")


  // Partial application of split which can then be

  // applied to a string to retrieve the contained words


  let words = String.split [' ']


  // And here's where we plug everything together...


  Seq.untyped_to_list ms @ Seq.untyped_to_list ps |>

    List.map extractText |> List.flatten |> List.map words |>

    List.flatten |> Set.of_list |> Set.to_list |> printList


  // As usual, committing is cheaper than aborting


  tr.Commit()

Hopefully the beginning section is understandable, given what we usually need to do from a C# or VB.NET program. So I'll start my descriptions from the extractText function.

extractText takes an ObjectId and uses the open transaction to open it for read. Then, depending on the type of the object, it gets the text contained and returns it within a list (currently containing either 0 or 1 member, depending). Currently this is only implemented for MText objects, but it could very easily be extended to handle other textual objects. For non-MText objects (matched by the wildcard character '_') an empty list ([]) is returned.

printList is a recursive function which uses our old friend Editor.WriteMessage() to write the "head" of the list (h) to the command-line, and then recurses to print the "tail" of the list (t). When the list is empty, we simply print a newline character and return.

words is a function defined by partial application of String.split, which typically takes two arguments - a list of characters to consider delimiters, plus a string to split. So you would call it using:

String.Split [' '] "This is my string"

which would return a list of strings:

["This", "is", "my", "string"]

Our definition of the function words actually allows us to get the same results by passing one argument:

words "This is my string"

Now we get to the guts of the command, which I'm going break down call-by-call:

Seq.untyped_to_list ms @ Seq.untyped_to_list ps |>

Here we get a list of the ObjectIds from the modelspace and append it (@) to the ObjectIds of the contents of the first paperspace layout. BlockTableRecord implements IEnumerable - aka "seq" in F# - so we use untyped_to_list. If it implemented the more modern, generics-derived IEnumerable<ObjectId> we would use Seq.to_list instead. We then pass the results of this operation - a list of ObjectIds - to the next in the chain using the pipeline operator (|>).

List.map extractText |>

Here we call the extractText function on each of the items in the list passed in (the list of ObjectIds), and the results of this operation get returned in a new list. The extractText function returns a list of strings for each ObjectId, so the result of this "map" is a list of a list of strings. Which gets piped to the next function.

List.flatten |>

As we have a list of a list of strings, we "flatten" it to only have a list of strings. And we pipe it on.

List.map words |>

Now we map our words function to return the list of words contained in each string in the list. This - once again - gives us a list of a list of strings.

List.flatten |>

Which - once again - we flatten to a list of strings (now a list of individual words).