Through the Interface: Implementing a simple graphing tool inside AutoCAD using F#

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


« Nesting instincts: getting more out of transactions inside AutoCAD using .NET | Main | Importing and pixelizing images inside AutoCAD using F# »

January 28, 2009

Implementing a simple graphing tool inside AutoCAD using F#

Well, I couldn't resist... as I mentioned in the last post - where we looked at creating a simple graph inside AutoCAD as an example of modifying objects inside nested transactions - the idea of graphing inside AutoCAD is a good fit for F#. This is for a number of reasons: F# is very mathematical in nature and excels at processing lists of data. I also spiced it up a bit by adding some code to parallelise some of the mathematical operations, but that didn't turn out to be especially compelling with my dual-core laptop. More on that later.

Here's the F# code:

// Use lightweight F# syntax


// Declare a specific namespace and module name

module Grapher.Commands

// Import managed assemblies

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.DatabaseServices

open Autodesk.AutoCAD.Geometry

// Define a common normalization function which makes sure

// our graph gets mapped to our grid

let normalize fn normFn x minInp maxInp maxOut =

  let res =

    fn ((maxInp - minInp) * x / maxOut)

  let normRes = normFn res

  if normRes >= 0.0 && normRes <= 1.0 then

    normRes * (maxOut - 1.0)



// Define some shortcuts to the .NET Math library

// trigonometry functions

let sin x = System.Math.Sin x

let cos x = System.Math.Cos x       

let tan x = System.Math.Tan x       

// Implement our own normalized trig functions

// which each map to the size of the grid passed in

let normSin max x =

  let nf a = (a + 1.0) / 2.0 // Normalise to 0-1

  let res =


      sin nf (Int32.to_float x)

      0.0 (2.0 * System.Math.PI) (Int32.to_float max)

  Int32.of_float res

let normCos max x =

  let nf a = (a + 1.0) / 2.0 // Normalise to 0-1

  let res =


      cos nf (Int32.to_float x)

      0.0 (2.0 * System.Math.PI) (Int32.to_float max)

  Int32.of_float res

let normTan max x =

  let nf a = (a + 3.0) / 6.0 // Normalise differently for tan

  let res =


      tan nf (Int32.to_float x)

      0.0 (2.0 * System.Math.PI) (Int32.to_float max)

  Int32.of_float res

// Now we declare our command


let gridCommand() =

  // We'll time the command, so we can check the

  // sync vs. async efficiency

  let starttime = System.DateTime.Now

  // Let's get the usual helpful AutoCAD objects

  let doc =


  let ed = doc.Editor

  let db = doc.Database

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

  use tr =


  // Get appropriately-typed BlockTable and BTRs

  let bt =



    :?> BlockTable

  let ms =




    :?> BlockTableRecord

  // Function to create a filled circle (hatch) at a

  // specific location

  // Note the valid use of tr and ms, as they are in scope

  let createCircle pt rad =

    let hat = new Hatch()





    let id = ms.AppendEntity(hat)

    tr.AddNewlyCreatedDBObject(hat, true)

    // Now we create the loop, which we make db-resident

    // (appending a transient loop caused problems, so

    // we're going to use the circle and then erase it)

    let cir = new Circle()

    cir.Radius <- rad

    cir.Center <- pt

    let lid = ms.AppendEntity(cir)

    tr.AddNewlyCreatedDBObject(cir, true)

    // Have the hatch use the loop we created

    let loops = new ObjectIdCollection()

    loops.Add(lid) |> ignore

    hat.AppendLoop(HatchLoopTypes.Default, loops)


    // Now we erase the loop



  // Function to create our grid of circles

  let createGrid size rad offset =

    let ids = new ObjectIdCollection()

    for i = 0 to size - 1 do

      for j = 0 to size - 1 do

        let pt =

          new Point3d

            (offset * (Int32.to_float i),

            offset * (Int32.to_float j),


        let id = createCircle pt rad

        ids.Add(id) |> ignore


  // Function to change the colour of an entity

  let changeColour col (id : ObjectId) =

    if id.IsValid then

      let ent =

        tr.GetObject(id, OpenMode.ForWrite) :?> Entity

      ent.ColorIndex <- col

  // Shortcuts to make objects red and yellow

  let makeRed = changeColour 1

  let makeYellow = changeColour 2

  // Function to retrieve the contents of our

  // array of object IDs - this just calculates

  // the index based on the x & y values

  let getIndex fn size i =

    let res = fn size i

    if res >= 0 then

        (i * size) + res



  // Apply our function synchronously for each value of x

  let applySyncBelowMax size fn =

    [| for i in [0..size-1] ->

       getIndex fn size i |]

  // Apply our function asynchronously for each value of x

  let applyAsyncBelowMax size fn =



        [ for i in [0..size-1] ->

          async { return getIndex fn size i } ])

  // Hardcode the size of the grid and create it

  let size = 50

  let ids = createGrid size 0.5 1.2

  // Make the circles all red to start with

  Seq.iter makeRed (Seq.cast ids)

  // From a certain index in the list, get an object ID

  let getId i =

    if i >= 0 then




  // Apply one of our trig functions, synchronously or

  // otherwise, to our grid

  applySyncBelowMax size normSin |> getId |>

      Array.iter makeYellow

  // Commit the transaction


  // Check how long it took

  let elapsed =


        (System.DateTime.Now, starttime)


    ("\nElapsed time: " +



Here's what you see on AutoCAD's drawing canvas when you run the GRAPH command as it stands:

Sine from F#

If you want to play around with other functions, you can edit the call to applySyncBelowMax to pass normCos or normTan instead of normSin.

Cosine from F#

Tangent from F#


As I mentioned earlier, if you swap the call to be applyAsyncBelowMax instead of applySyncBelowMax you will actually run the mathematics piece as asynchronous tasks. These are CPU-bound operations - they don't call across the network or write to a hard-drive, which might have increased the benefit of calling them asynchronously - so right now the async version actually runs more slowly than the sync version. If I were to have more processing cores available to me, it might also give us more benefit, but right now with my dual-core machine there's more effort spent coordinating the tasks than you gain from the parallelism. But I'll let you play around with that yourselves... you may get better results. One other note on that piece of the code: at some point I'd like to make use of the Parallel Extensions for .NET (in particular the Task Parallel Library (TPL)), but for now I've continued with what I know, the asynchronous worklows capability which is now standard in F#.

I'm travelling in India this week (and working from our Bangalore office next week), so this is likely to be my last post of the week.


TrackBack URL for this entry:

Listed below are links to weblogs that reference Implementing a simple graphing tool inside AutoCAD using F#:

blog comments powered by Disqus


10 Random Posts