Kean Walmsley

July 2009

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  

Twitter Updates

    follow me on Twitter



    « Using Reflector to diagnose tail call optimization in F# | Main | New APIs in AutoCAD 2009 »

    February 29, 2008

    Recursive F# code to generate random point clouds inside AutoCAD

    At the beginning of the week, we looked at some iterative F# code to generate random point clouds inside AutoCAD. We then took the time to use Reflector to dig under the hood and understand why the previous recursive implementation was causing stack problems.

    For completeness (and - I admit it - being driven slightly by laziness, as this is a quick post to crank out :-) here's the recursive version of the random point cloud generation code in F#:

    // Use lightweight F# syntax


    #light


    // Declare a specific namespace and module name


    module MyNamespaceRecursive.MyApplication


    // Import managed assemblies


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

    #r "acdbmgd.dll"

    #r "acmgd.dll"


    open Autodesk.AutoCAD.Runtime

    open Autodesk.AutoCAD.ApplicationServices

    open Autodesk.AutoCAD.DatabaseServices

    open Autodesk.AutoCAD.Geometry


    // Get a random vector on a plane


    let randomVectorOnPlane pl =


      // Create our random number generator

      let ran = new System.Random()


      // First we get the absolute value

      // of our x, y and z coordinates


      let absx = ran.NextDouble()

      let absy = ran.NextDouble()


      // Then we negate them, half of the time


      let x = if (ran.NextDouble() < 0.5) then -absx else absx

      let y = if (ran.NextDouble() < 0.5) then -absy else absy


      let v2 = new Vector2d(x,y)

      new Vector3d(pl,v2)


    // Get a random vector in 3D space


    // Note: _ is only used to make sure this function gets

    // executed when it is called... if we have no argument

    // it's a value that doesn't require repeated execution


    let randomVector3d _ =


      // Create our random number generator

      let ran = new System.Random()


      // First we get the absolute value

      // of our x, y and z coordinates


      let absx = ran.NextDouble()

      let absy = ran.NextDouble()

      let absz = ran.NextDouble()


      // Then we negate them, half of the time


      let x = if (ran.NextDouble() < 0.5) then -absx else absx

      let y = if (ran.NextDouble() < 0.5) then -absy else absy

      let z = if (ran.NextDouble() < 0.5) then -absz else absz


      new Vector3d(x, y, z)


    // Now we declare our command


    [<CommandMethod("ptsr")>]

    let createPoints () =


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

      // a list of random points on its surface


      let rec getNPoints n (sol:Solid3d) ptlist =

        if n <= 0 then

          ptlist

        else

          let mp = sol.MassProperties


          let pl = new Plane()


          pl.Set(mp.Centroid,randomVector3d n)

          let reg = sol.GetSection(pl)

          let ray = new Ray()

          ray.BasePoint <- mp.Centroid

          ray.UnitDir <- randomVectorOnPlane pl


          let pts = new Point3dCollection()

          reg.IntersectWith

            (ray,

            Intersect.OnBothOperands,

            pts,

            0, 0)


          pl.Dispose()

          reg.Dispose()

          ray.Dispose()


          getNPoints

            (n - pts.Count) sol

            (ptlist @ Seq.untyped_to_list pts)


      let generatePoints numPoints (x : ObjectId) =

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

        match obj with

        | :? Solid3d ->

          let sol = (obj :?> Solid3d)

          getNPoints numPoints sol []

        | _ -> []


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


      let rec drawPointList (x:Point3d list) =

        match x with

        | [] -> ()

        | h :: t ->

          ed.DrawVector(h,h,1,true)

          drawPointList t


      // Let's generate 100K points per solid


      let points = generatePoints 100000


      // Here's where we plug everything together...


      Seq.untyped_to_list ms |> // ObjectIds from modelspace

        List.map points |>      // Get points for each object

          List.concat |>        // No need for the outer list

            drawPointList       // Draw the resultant points


      // As usual, committing is cheaper than aborting


      tr.Commit()

    This code is much more elegant from a functional perspective, and F#'s tail call optimization means the resultant code runs just as efficiently as if we'd used iterative code with mutable state. To make the code optimizable, I had to adjust the arguments to the genNPoints function to accept an accumulator object (being the list of points generated thus far). Appending to this list, rather than the one being returned by the next recursion call, allows F# to optimize the recursion into a loop.

    Thanks to Namin for his comments on the last post - they were very helpful to my understanding of the problem.

    Next week I'm going to try to spend some time diving into the new APIs coming with AutoCAD 2009.

    TrackBack

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

    Listed below are links to weblogs that reference Recursive F# code to generate random point clouds inside AutoCAD:

    Comments

    Verify your Comment

    Previewing your Comment

    This is only a preview. Your comment has not yet been posted.

    Working...
    Your comment could not be posted. Error type:
    Your comment has been posted. Post another comment

    The letters and numbers you entered did not match the image. Please try again.

    As a final step before posting your comment, enter the letters and numbers you see in the image below. This prevents automated programs from posting comments.

    Having trouble reading this image? View an alternate.

    Working...

    Post a comment

    Feed & Share

    Search