Initialization code in your F# AutoCAD application
Back from a nice long weekend, although I spent most of it sick with a cold. I find this increasingly the way with me: I fend off illness for months at a time (probably through stress, truth be told) but then I get a few days off and wham. A shame, as we had a huge dump of snow over the weekend... we get white Christmases here every five years or so, but it's really uncommon to get a white Easter.
I had a very interesting question come in by email from 冷血儿, who wanted to get the technique shown in this post working in his F# application.
Here's the F# code I managed to put together after consulting hubFS, in particular:
#light
namespace MyNamespace
open Autodesk.AutoCAD.Runtime
open Autodesk.AutoCAD.ApplicationServices
type InitTest() =
class
let ed =
Application.DocumentManager.MdiActiveDocument.Editor
interface IExtensionApplication with
member x.Initialize() =
ed.WriteMessage
("\nInitializing - do something useful.")
member x.Terminate() =
printfn "\nCleaning up..."
end
end
module MyApplication =
let ed =
Application.DocumentManager.MdiActiveDocument.Editor
[<CommandMethod("TST")>]
let f () =
ed.WriteMessage("\nThis is the TST command.")
[<assembly: ExtensionApplication(type InitTest)>]
do
ed.WriteMessage("\nModule do")
Here's what happens when we load our module and run the TST command:
Command: NETLOAD
Module do
Initializing - do something useful.
Command: TST
This is the TST command.
March 25, 2008 in AutoCAD, AutoCAD .NET, F#, Notification / Events | Permalink | Comments (0) | TrackBack
A simple taxonomy of programming languages
Someone asked me recently how I categorize different programming paradigms. I thought it was a very interesting question, so here's what I responded. Please bear in mind that this is very much the way I see things, and is neither an exhaustive nor a formally-ratified taxonomy.
One way to look at languages is whether they're declarative or imperative:
Declarative programming languages map the way things are by building up “truths”: this category includes functional programming languages (such as Miranda, Haskell and Erlang) which tend to be mathematical in nature (you define equations) and start with lambda calculus as a foundation. The other main set of declarative languages are logic programming languages (such as Prolog), which start with propositional calculus as a foundation (you declare axioms that build up to a system against which you can run queries). Declarative languages tend to focus on describing the problem to solve, rather than how to solve it.
Imperative programming languages, on the other hand, are lists of instructions of what to do: I tend to consider procedural programming languages (such as C, COBOL, Fortran and Pascal) as a sub-category which focus on the definition and execution of sub-routines, while some people treat the terms imperative and procedural as synonyms.
Considering these definitions, object-oriented programming languages (such as Smalltalk and Eiffel) should probably be considered declarative, as conceptually they map real-world objects, but the truth is that the most popular OO languages (such as C++) are impure, and so most OO systems combine big chunks of procedural (i.e. imperative) code. Many people who think they’re doing OOP are actually packaging up procedures.
Note that I've tried not to list multi-paradigm languages such as Ada, C++ and F# in the above categorisation. It's possible that some of the languages I've listed are also multi-paradigm, but anyway.
One other way to think about languages is whether they’re top-down or bottom-up:
Bottom-up languages are ultimately layered on how a processor works (from machine code to assembly language to C & C++), while top-down languages start from the world of mathematics and logic and add language features that allow them to be used for programming (i.e. declarative languages are therefore top-down). This latter set of languages are starting to see increased adoption, as they assume much less (even nothing) about the underlying machinery, in which big changes are occurring with multiple processing cores being introduced (which essentially invalidate the assumptions of previous generations of programmers, who have been conditioned to think in terms of the processor's ability to store and access state).
Many popular - or soon to be popular - programming environments are pragmatic in nature: C++ allows OOP but can also be used for procedural programming, VB.NET now allows you to define and access objects while coming from a long line of procedural languages, F# is multi-paradigm, combining OO with functional and imperative programming.
There are bound to be people with differing views on this subject (and many of them are no doubt more intelligent and experienced in these matters than I), but this is how I would answer the question of how to categorise programming languages.
For those of you with an interest in the future of programming languages, I can strongly recommend the following Channel 9 episodes. If you're not aware of Channel 9, then prepare to be impressed: Microsoft has given a fantastic gift to the development community with this resource.
Burton Smith: On General Purpose Super Computing and the History and Future of Parallelism
Erik Meijer: Functional Programming
Anders Hejlsberg, Herb Sutter, Erik Meijer, Brian Beckman: Software Composability and the Future of Languages
Brian Beckman: Don't fear the Monads
Joe Armstrong - On Erlang, OO, Concurrency, Shared State and the Future, Part 1
Joe Armstrong - On Erlang, OO, Concurrency, Shared State and the Future, Part 2
Enjoy! :-)
March 17, 2008 in Concurrent programming, F# | Permalink | Comments (2) | TrackBack
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.
- 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
Here's what happens when we run the PTS command on a set of 6 solids:
We can see the points disappear when we perform a 3DORBIT:
When we exit the orbit, our old point graphics are displayed...
Until the event handler kicks in and updates the display of our points:
March 10, 2008 in AutoCAD, AutoCAD .NET, F#, ObjectARX | Permalink | Comments (5) | TrackBack
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.
February 29, 2008 in AutoCAD, AutoCAD .NET, F# | Permalink | Comments (0) | TrackBack
Using Reflector to diagnose tail call optimization in F#
I've talked about Lutz Roeder's Reflector tool a couple of times and it's proven to be very useful to me, once again.
I mentioned in my last post about some problems I was having with tail recursion, and my choice to replace certain recursive functions with iterative versions. Today we're going to use Reflector to take a look under the hood of some compiled assemblies, to determine which recursive functions have been optimised correctly and which have not.
Let's start by taking a look at the two recursive functions I mentioned last time, starting with the most simple:
// 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
Here's what we see when we open the compiled assembly in Reflector and disassemble the equivalent function into C#:
[Serializable]
internal class drawPointList@140 : FastFunc<List<Point3d>, Unit>
{
// Fields
public Editor ed;
// Methods
public drawPointList@140(Editor ed)
{
this.ed = ed;
}
public override Unit Invoke(List<Point3d> x)
{
while (true)
{
List<Point3d> list = x;
if (!(list is List<Point3d>._Cons))
{
break;
}
List<Point3d> list2 = ((List<Point3d>._Cons) list)._Cons2;
Point3d from = ((List<Point3d>._Cons) list)._Cons1;
this.ed.DrawVector(from, from, 1, true);
x = list2;
}
return null;
}
}
You can see that the implementation of Invoke contains a while loop, which loops forever (until the list is empty), rather than calling recursively into itself.
Now let's take a look at the other, more complex function, which generates a list of points via recursion:
// A function that accepts an ObjectId and returns
// a list of random points on its surface
let rec getNPoints n (sol:Solid3d) =
if n <= 0 then
[]
else
let mp = sol.MassProperties
let pl = new Plane()
pl.Set(mp.Centroid,randomVector3d sol.ObjectId.OldId)
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()
let ptlist = Seq.untyped_to_list pts
ptlist @ getNPoints (n - pts.Count) sol
This function, when compiled by the F# compiler integrated into Visual Studio and disassembled by Reflector, equates to this C# code:
[Serializable]
internal class getNPoints@101T<T> : OptimizedClosures.FastFunc2<int, Solid3d, List<T>>
{
// Fields
public TypeFunc _self0;
// Methods
public getNPoints@101T(TypeFunc _self0)
{
this._self0 = _self0;
}
public override List<T> Invoke(int n, Solid3d sol)
{
TypeFunc func = this._self0;
int num = n;
int t = 0;
if ((num > t) ^ true)
{
return List<T>.Nil_uniq;
}
Solid3dMassProperties h = sol.MassProperties;
Plane plane = new Plane();
plane.Set(h.Centroid, MyApplication.randomVector3d<int>(sol.ObjectId.OldId));
Region section = sol.GetSection(plane);
Ray entityPointer = new Ray();
entityPointer.BasePoint = h.Centroid;
entityPointer.UnitDir = MyApplication.randomVectorOnPlane<Plane>(plane);
Point3dCollection points = new Point3dCollection();
section.IntersectWith(entityPointer, Intersect.OnBothOperands, points, 0, 0);
plane.Dispose();
section.Dispose();
entityPointer.Dispose();
List<T> list = Seq.untyped_to_list<Point3dCollection, T>(points);
Solid3d u = sol;
int num2 = n - points.Count;
return Pervasives.op_Append<T>(FastFunc<int, Solid3d>.InvokeFast2<List<T>>((FastFunc<int, FastFunc<Solid3d, List<T>>>) func.Specialize<T>(), num2, u), list);
}
}
While a little harder to follow (please don't worry about the details), we can see that the recursive tail call has not been optimised out, as we don't have an iterative construct (e.g. a while loop). Instead we see a call to InvokeFast2 (which will result in Invoke being called recursively), prior to the value being returned.
Why exactly this case hasn't been handled isn't clear to me - in fact I'm pretty sure it's a bug in F#, which I will go ahead and report - but it is clear that (at least for now) we need to change the implementation if we're to avoid a stack overflow. And that's fine: the last post contains an iterative version we can use.
Finallly we're going to take a look at parts of the assembly created by building the code in the recent Robotic Hatching posts. As I mentioned previously, I ended up hitting problems when creating 400 or so segments in the polyline hatch when using my F# implementation. Rather than automatically changing each of the recursive functions (those prefixed by "let rec") to iterative versions, I checked the disassembly listings one by one until I found the problematic one:
// A recursive function to get the points
// for our path
let
if times <= 0 then
[]
else
try
let pt =
nextBoundaryPoint cur start doTrace
(pt :: definePath pt (times-1))
with exn ->
if exn.Message = "Could not get point" then
definePath start times
else
failwith exn.Message
rec definePath start times =Here's how the compiled (and then disassembled/reassembled) function looks in C#:
[Serializable]
internal class definePath@281 : OptimizedClosures.FastFunc2<Point3d, int, List<Point3d>>
{
// Fields
public Curve cur;
public bool doTrace;
// Methods
public definePath@281(bool doTrace, Curve cur)
{
this.doTrace = doTrace;
this.cur = cur;
}
public override List<Point3d> Invoke(Point3d start, int times)
{
int num = times;
int num2 = 0;
if ((num > num2) ^ true)
{
return List<Point3d>.Nil_uniq;
}
try
{
Point3d pt = Commands.nextBoundaryPoint(this.cur, start, this.doTrace);
return new List<Point3d>._Cons(pt, FastFunc<Point3d, int>.InvokeFast2<List<Point3d>>(this, pt, times - 1));
}
catch (object obj1)
{
Exception exn = (Exception) obj1;
string message = exn.Message;
string b = "Could not get point";
if (string.Equals(message, b))
{
return FastFunc<Point3d, int>.InvokeFast2<List<Point3d>>(this, start, times);
}
return Operators.failwith<List<Point3d>>(exn.Message);
}
}
}
This one is actually only recurses when we catch an exception we expect to occur (and actually have thrown ourselves elsewhere in the code) occasionally during run-time. A better implementation - one that F#'s compiler is able to tail call optimise and meets my aesthetic requirements - is here:
// An iterative function to get the points
// for our path
let definePath start times =
let pts = new Point3dCollection()
pts.Add(start) |> ignore
let mutable i = 0
while i < times do
try
let pt =
nextBoundaryPoint
cur pts.[pts.Count-1] doTrace runAsync
pts.Add(pt) |> ignore
i <- i + 1
with exn ->
if exn.Message <> "Could not get point" then
failwith exn.Message
Seq.untyped_to_list pts
I won't bother showing this function's C# equivalent, as determined by Reflector, you should just trust me that it's better. :-)
I did change the implementation slightly, so we no longer add our first vertex when we create the polyline - we simply add it to the array of vertices, which is cleaner - and we also have to pass a start index of 0 into the definePath function, rather than 1. Very minor changes, but they actually do make the code slightly simpler.
Anyway, this kind of analysis and control is made possible by the fact that F# compiles to IL, just like the other .NET languages. This allows us to take advantage of existing tools for code analysis & obfuscation, and ultimately give us better, more consistent control than we would get from a functional programming implementation outside of .NET.
February 27, 2008 in AutoCAD, AutoCAD .NET, F#, Visual Studio | Permalink | Comments (7) | TrackBack
Pointing at clouds: more random musings on AutoCAD and F#
On my way back from the US last week, I started thinking more about uses for random numbers inside AutoCAD: especially ones that allow me to try out some possible application areas for F#.
There's something deliciously perverse about using random numbers in Engineering systems, where it's really important for outcomes to be deterministic (i.e. predictable) & precise. And that perversity appeals to me quite strongly, for some reason. Feel free to drop me a mail if you have an idea why that might be, any amateur psychologists out there... ;-)
So, I got to thinking... an interesting domain area for our development partners is that of laser scanning. These systems often generate huge (and I mean huge) data-sets: millions upon millions of points are generated by laser scanners (often known as point clouds), and these need to be managed in some way by software - often by solutions that are integrated with Autodesk products.
So I thought, wouldn't it be fun to go through an AutoCAD model and generated huge arrays of points representing the shell of solid objects in the model - essentially generating random point clouds - to see how F# deals with such humungous data-sets.
The general approach I decided to use was to go through all of the entities in the model-space, open them up and - for those that are of type Solid3d - generate 100,000 points (say) that are found on the shell of the object. To find points on the shell, I adopted the following technique:
- Create a random 3D vector
- Generate a random plane intersecting the solid (by using the centroid of the solid as the origin and the generated vector as the normal)
- Create a region representing the section of the solid and the plane
- Create another random vector, this time on the plane
- Use this vector to fire a ray
- Get the intersection of the ray and the region
- Repeat until we have enough points
To display each point we just draw a zero-length vector, which I decided to make red.
In my initial implementation, I used a classic functional programming technique, that of tail recursion. As a bit of a purist, I try to use functional (as opposed to imperative) techniques whenever I can when writing F# code. The problem is that tail recursion can significantly drain your stack space, as it results in a new function call - and a new level in the call stack - for every item in your list (and after all we're talking about huge numbers of points). Some compilers/evaluation systems can optimise out the recursive function call, reducing it to a simple branch, but F# doesn't currently appear to do so with my code (it is supposed to, so there may be something about the way my recursive functions are structured that prevents them from being identified as tail-recursive).
Here are a couple of examples of tail-recursive functions - one to generate a list of points, the other to draw them:
// A function that accepts an ObjectId and returns
// a list of random points on its surface
let rec getNPoints n (sol:Solid3d) =
if n <= 0 then
[]
else
let mp = sol.MassProperties
let pl = new Plane()
pl.Set(mp.Centroid,randomVector3d sol.ObjectId.OldId)
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()
let ptlist = Seq.untyped_to_list pts
ptlist @ getNPoints (n - pts.Count) 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
| [] -> ()
While more elegant from a functional perspective, this elegance results in sub-optimal execution. The way to "fix" this is to introduce iterative code - whether a for, foreach or while loop - to avoid the recursion.
Here's the complete F# code that makes use of iterative techniques rather than recursion:
// 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 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("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 getNPoints n (sol:Solid3d) =
let ptcol = new Point3dCollection()
while ptcol.Count < n do
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()
for pt in pts do
ptcol.Add pt |> ignore
Seq.untyped_to_list ptcol
let generatePoints numPoints (x : ObjectId) =
let obj = tr.GetObject(x,OpenMode.ForRead)
match obj with
| :? Solid3d ->
let sol = (obj :?> Solid3d)
getNPoints numPoints sol
| _ -> []
// An iterative function to draw a point list
let drawPointList (x:Point3d list) =
for pt in x do
ed.DrawVector(pt,pt,1,true)
// Let's generate 10K 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()
Here are the results of the PTS command called on a set of 6 Solid3d objects, essentially generating and drawing 600,000 points. The code doesn't currently result in any persistent graphics at all - if you change views the points all disappear. At some point I'd like to extend this to store the generated points persistently, but for now the point of the exercise (no pun intended :-) is more to see how F# performs with large data-sets, rather than how we deal with the issue of persistence.
A final note: I now realise the use of tail recursion was almost certainly the wall I hit with the F# implementation in this previous post.
February 25, 2008 in AutoCAD, AutoCAD .NET, F# | Permalink | Comments (0) | TrackBack

Atom
