September 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        








« New APIs in AutoCAD 2009 | Main | Getting the full path of the active drawing in AutoCAD using .NET »

March 10, 2008

Using AutoCAD 2009's new transient graphics API to show point clouds from F#

To start off my series of more in-depth looks at the new APIs provided in AutoCAD 2009, I decided to extend some recently posted F# code to generate and draw transient point clouds to be slightly less transient: we'll see how to use the new transient graphics API in AutoCAD to display a cache of transient graphics, even after the view has been changed.

Some of you may be wondering about the amount of code I'm posting in F#. I find the technology extremely interesting and am also increasingly productive with it, so I've found myself gravitating towards using it more for my blog samples. I understand it's not for everyone, and I will definitely continue to post C# on a regular basis, but as I'm currently spending quite a lot of time on F# I'm somewhat selfishly posting what I'm doing, rather than duplicating effort.

A few people have asked me by email "so should I be learning F# now, rather than C#?". I generally recommend to people to carry on learning how to program in C# (or VB.NET, for that matter, although I personally prefer the syntax in C#), as this skill is currently more relevant in the industry than F# programming. I really like F#, but for me it's another (for now, secondary) tool for solving certain classes of problem. I will say, however, that learning functional programming makes you a better programmer overall, and FP techniques are making their way into more mainstream languages, such as VB.NET and C#. For instance, today we're going to be using a lambda expression to register an event-handler: anonymous functions or lambda expressions are now part of C#.

Why does FP make you a better programmer? Because it leads you away from relying on shared state and side-effects. I won't get into the details of that now, but reducing your reliance on shared state is a good thing for your code: at some point in the future it will more easily harness parallel processing capabilities such as multicore chips. So even if you don't use F# on a daily basis, the way that you look at problems after you've understood its fundamental approach could - one day - have significant implications on your code's performance.

I digress, although this has reminded me that I've been meaning to post a comparative piece on programming technologies, sometime soon.

Here's the F# code from this previous post, modified to make use of the transient graphics API in AutoCAD 2009 to draw its points (rather than using Editor.DrawVector() with a zero-length vector, which is how we did it last time).

// Use lightweight F# syntax


#light


// Declare a specific namespace and module name


module MyNamespaceRecursive.MyApplication


// Import managed assemblies


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

#r "acdbmgd.dll"

#r "acmgd.dll"


open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.DatabaseServices

open Autodesk.AutoCAD.Geometry

open Autodesk.AutoCAD.GraphicsInterface


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


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)


// Create some state to store information about

// the current view. We use this to determine

// when we need to update our transient

// graphics.


let mutable vd = new Vector3d(0.0,0.0,0.0)

let mutable vt = 0.0

let mutable vh = 0.0


// Check the view against our stored info:

// if anything has changed, update the

// cache and return true.


let viewChanged (vtr : ViewTableRecord) =

  if (vd <> vtr.ViewDirection ||

      vt <> vtr.ViewTwist ||

      vh <> vtr.Height) then

    vd <- vtr.ViewDirection

    vt <- vtr.ViewTwist

    vh <- vtr.Height

    true

  else

    false


// Here's where we'll store our list of DBPoint objects

// to be redrawn


let mutable savedpts = []


// Now we declare our command


[<CommandMethod("pts")>]

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())

      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 []

    | _ -> []


  // Create a DBPoint from a Point3d


  let to_db_point pt =

    let dbp = new DBPoint(pt)

    dbp.ColorIndex <- 1

    dbp


  // Add a single point (or any "drawable" object, for that

  // matter) to the transient graphics manager.


  let drawTransient x =

    let tm = TransientManager.CurrentTransientManager

    let ic = new IntegerCollection()

    tm.AddTransient

      (x, TransientDrawingMode.DirectShortTerm, 0, ic)

        |> ignore


  // We'll generate 100K points per solid

  // (the below line simply defined a new function

  // by currying (fixing one argument for) another

  // function)


  let points = generatePoints 100000


  // Save the points we generate in our mutable state


  savedpts <-

    Seq.untyped_to_list ms |>  // ObjectIds from modelspace

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

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

          List.map to_db_point // Get DBPoints


  // And then add each point to the transient graphics system


  List.iter drawTransient savedpts


  // As usual, committing is cheaper than aborting


  tr.Commit()


  // Add an event handler to respond to the doc-lock changed

  // event. This happens after every doc-centric command

  // (for instance), so we check whether the view has changed

  // before starting a potentially time-consuming operation.


  Application.DocumentManager.DocumentLockModeChanged.Add

    (fun _ ->

      if viewChanged (ed.GetCurrentView()) then

        for pt in savedpts do

          let tm = TransientManager.CurrentTransientManager

          let ic = new IntegerCollection()

          tm.UpdateTransient(pt, ic) |> ignore)

Some interesting points about this code:

  • We now store a list (potentially a very big list) of points in memory, in the savedpts variable
    • These are DBPoints, as they need to be "drawable" to be managed by the transient graphics subsystem
  • The use of the new transient graphics API is in the drawTransient function, which does the equivalent of this C# call:
    • Autodesk.AutoCAD.GraphicsInterface. TransientManager.CurrentTransientManager.AddTransient(pt, TransientDrawingMode.DirectShortTerm, 0, new IntegerCollection());
    • This API can be used to draw any "drawable" object - it doesn't have to be one that would typically be stored in the DWG file. It can be used to display custom glyphs and tooltips, for instance
      • Check out the ObjectARX (C++) sample under ObjectARX 2009\samples\graphics\AsdkTransientGraphicsSampFolder for more details
  • We register an event handler to update the display of these points when the view has changed
    • We do not currently have a viewChanged event exposed through the managed API, so we check for DocumentLockChanged and then see whether the view has changed there
      • We store some state about the previous view, so we know when it has changed
    • This event handler calls the equivalent of this C# code:
      • Autodesk.AutoCAD.GraphicsInterface. TransientManager.CurrentTransientManager.UpdateTransient(pt, new IntegerCollection());
    • See how easy it is to register an event handler in F#: the use of lambda expressions makes this really trivial (no need to define a function that we specify as a delegate). We also use the underscore ("_") to state we don't care about the arguments passed to the event handler, in this particular situation. Very neat.

Here's what happens when we run the PTS command on a set of 6 solids:

Big cloud in 2009

We can see the points disappear when we perform a 3DORBIT:

Orbiting big cloud in 2009

When we exit the orbit, our old point graphics are displayed...

Graphics catching up in 2009

Until the event handler kicks in and updates the display of our points:

Graphics updated in 2009

TrackBack

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

Listed below are links to weblogs that reference Using AutoCAD 2009's new transient graphics API to show point clouds from F#:

blog comments powered by Disqus

Feed/Share

10 Random Posts