Kean Walmsley


  • About the Author
    Kean on Google+

July 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 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:

blog comments powered by Disqus

10 Random Posts