Last summer I was wandering around in a Cambridge bookshop, when I came across at Robert Pickering's book: "Foundations of F#". I spent a couple of hours reading about the F# language (of course after I bought the book), an interesting mix of functional and imperative programming style.
I started programming in AutoCAD several years ago using AutoLisp first and ADS/ARX C++ environment later. Even though AutoLisp has very little to do with the Lisp language I had the chance to see some aspects of functional languages. Now is the time of .NET languages but I have to say sometime I miss the neat style that just a functional language provides. This is the reason I decided to invest a bit of time learning F#.
In this article I will show you how to write an Autodesk Inventor add-in using F#.
Of course you need Autodesk Inventor (if you are reading this article probably it is already installed on your PC). Download the F# package from the Microsoft Research website: (http://research.microsoft.com/fsharp/fsharp.aspx) . The release I used to write the add-in is FSharp-1.9.2.9.
It's possible to write software for Autodesk Inventor in several ways: I decided to write an Add-in because is the most powerful way you can have to communicate with the internal API. Inventor was designed to support clients that support COM technologies, this make the task of writing an Add-in very easy using VB6, but you will face more complexities using a .NET language.
F# language is very well designed to work in Visual Studio 2005. The setup automatically add an Add-in for Visual Studio 2005. Even though the Add-in is not really stable yet, you will be surprised of the good integration with the IDE. I will use this VS2005 for the job though this in not mandatory. Start Visual Studio and create a new F# project.

An Inventor Add-In is basically a dll so we have to force the F# compiler to make it instead of the default executable. Go to the project properties and set the project type to DLL. This will add the -a parameter to the compiler command line.

To have a working Add-in we need a strong-named add-in assembly and a strong-named Inventor Interop assembly. This is basically to avoid namespace conflicts once you add the assembly to the GAC. First you need to create a strong-name key file. This is quite easy, go to Start->Programs->Microsoft Visual Studio 2005 Tools->Microsoft Visual Studio 2005 Command Prompt and once in the command prompt move to the folder of your project. The reason we use the VS2005 command prompt is the PATH environment is correct in order to use the VS tools. Here you can type:
sn -k keyfile.snk
Visual Studio now creates a signature file for you. Go back to the project properties and set the Strong Naming to "Public Private Key File" and Strong name key file to your keyfile.snk. Now you need to reference the Inventor Interop Assembly. Unfortunately Autodesk does not provide this assembly yet so you have to create your own. Go to your Inventor folder and copy the "RxInventor.tlb" file from the "bin" folder to your project folder (where your keyfile.snk is located). Again, from the command prompt type:
tlbimp RxInventor.tlb /out:Inventor.dll /keyfile:keyfile.snk /primary /namespace:Inventor
this will create a strong-named Inventor interop assembly (Inventor.dll) avoiding conflicts. You will see lots of messages during the creation of the dll. You can basically ignore all of them. Go back to your project and reference the newly created dll. For F# this means adding a -r (we do not need to copy locally the dll, so we use the lowercase) before the name of the Inventor interop dll
Few lines of code are necessary to Inventor to connect to your add-in. Create a new source file "Inventor.fs" for your Visual Studio 2005 project.

Open the file and add the standard #light preprocessor definition to tell F# we require the light syntax. After we add few lines describing our assembly
[<System.Runtime.InteropServices.ComVisible(true);
System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.AutoDual);
System.Runtime.InteropServices.ProgIdAttribute("FSInventor.AddIn");
System.Runtime.InteropServices.GuidAttribute("DD9293F8-6881-3614-9FB4-11AAB3DCCBA1");
AssemblyVersion("1.0.*");
AssemblyKeyFile("keyfile.snk");
AssemblyCompany("QS Informatica");
AssemblyProduct("F# Sample Add-In");>]
Not all the declarations are actually necessary, but few of them are required to have a correct assembly. Then we declare a local namespace and reference some assemblies we are going to use in our add-in.
namespace FSInventorAddIn open Inventor open System open System.Reflection //.Assembly open System.Runtime.InteropServices open System.Windows.Forms open Microsoft.Win32
Now we can start writing the required code. We create a class exposing few methods that Inventor requires to be defined. To make the Add-In just a bit more interesting we will create a toolbar with a single button showing a welcome message. First we create a Button Class as a wrapper for the Inventor Button this will make the code clear (of course you need to know how Inventor works). A couple of points I want to show you: first, you can define F# comments in several ways:
// this is a single line comment
(* this is a for a multiline comment *)
/// this is a DOC comment.
You can tell the F# compiler to produce the source documentation at compiler time adding the -doc mydocfile.xml option to the command line:
fsc --fullpaths --progress -a -O3 --keyfile "E:\Src\F#\Inventor\InventorAddIn\keyfile.snk" -r Inventor.dll -doc InventtorAddIn.xml
Second, F# can infers type automatically, so it is not necessary to declare types. This is required only in few situations: just append a colon after the parameter declaration (inventor : Inventor.Application)
/// Wrap the Inventor Button with my button class
type Button = class
val mutable button : ButtonDefinition
val mutable buttonHandler : ButtonDefinitionSink_OnExecuteEventHandler
new () = {button = null; buttonHandler = null}
// add new button
member x.Add(inventor:Inventor.Application, dispname, intname, classification, clientid, descr, tooltip, stdicon, lrgicon, btndisplay) =
x.button <- inventor.CommandManager.ControlDefinitions.AddButtonDefinition(dispname, intname, classification, clientid, descr, tooltip, stdicon, lrgicon, btndisplay)
// set the button handler
member x.SetHandler(btndelegate) =
x.buttonHandler <- ButtonDefinitionSink_OnExecuteEventHandler(btndelegate)
x.button.add_OnExecute(x.buttonHandler)
// release the button
member x.Detach () =
x.button.remove_OnExecute(x.buttonHandler)
Marshal.ReleaseComObject(x.button) |> ignore
// the 'get' property
member x.InvButton with get() = x.button
endThe following code creates the basic Add-in server class. I attempted to create a complete Add-In but, in fact, just the "Activate" and "Deactivate" methods seem to be required for the Add-In to be valid. Let's have a look at the code. We explicitly declare the GUID for the Add-In: this make the Windows registry inspection easier. You can generate your own valid Add-In using lots of different tools. VS 2005 provide a simple utility to generate a valid GUID. If you come from VB6 then this is probably new: VB6 automatically generates a new GUID every time you recompile the project (you have to force the binary compatibility to avoid this behaviour).
// explicit the add-in registration GUID
[<GuidAttribute("5E707342-8849-11DC-8314-0800200C9A66")>]
type AddInServer = class
val mutable inventor : Inventor.Application
val buttonHandler : ButtonDefinitionSink_OnExecuteEventHandler
val button : Button
new () = {
inventor = null;
buttonHandler = null;
button = new Button() // add the new button here
}
// button callback
member x.OnButtonExecute(context : NameValueMap) =
System.Windows.Forms.MessageBox.Show("Hello from F#!") |> ignore
interface Inventor.ApplicationAddInServer with
member x.Activate(addInSiteObject : Inventor.ApplicationAddInSite, firstTime : bool) =
x.inventor <- addInSiteObject.Application
// create the button
x.button.Add(x.inventor, "F# Button",
"F#_Button_Internal",
Inventor.CommandTypesEnum.kShapeEditCmdType,
"{982C624F-77F7-42AA-8477-C86D027AFB46}",
"Description",
"Tooltip",
System.Type.Missing,
System.Type.Missing,
Inventor.ButtonDisplayEnum.kAlwaysDisplayText)
x.button.SetHandler(x.OnButtonExecute)
// the first time create the toolbar
if firstTime = true then
let commandbar = x.inventor.UserInterfaceManager.CommandBars.Add("F# Toolbar",
"F#_Toolbar_Internal",
CommandBarTypeEnum.kRegularCommandBar,
"{963308E2-D850-466D-A1C5-503A2E171552}")
// add the button to the toolbar
commandbar.Controls.AddButton(x.button.InvButton, 0) |> ignore
commandbar.Visible <- true
member x.Deactivate() =
// detach button
x.button.Detach()
Marshal.ReleaseComObject(x.inventor) |> ignore
x.inventor <- null
GC.WaitForPendingFinalizers()
GC.Collect() |> ignore
member x.ExecuteCommand(cmdID:int) =
ignore()
member x.Automation with get() = null
end
I think the code is self explanatory: you have to define the Activate/Deactivate methods the Inventor.ApplicationAddInServer Interface exposes. F# syntax is clear and the #light preprocessor definition makes sure you can use indentation (like Python) to correctly formatting your code.
Probably you have noticed some weird constructs like |> ignore:
commandbar.Controls.AddButton(x.button.InvButton, 0) |> ignore do not forget F# is a functional language (even though in this example we are using the imperative programming style). The AddButton method returns a value (CommandBarControl) and you can't just get rid of it. You have to turn this function into a function of type unit (a kind of void for C++ people). You can do this in several ways: bind the object to the _ identifier or just using the pass-forward operator to pass the returned value to the ignore function. This is a very powerful construct of F#, you will save hundreds of lines of code once you master the pass-forward operator.
let _ = commandbar.Controls.AddButton(x.button.InvButton, 0) ignore(commandbar.Controls.AddButton(x.button.InvButton, 0))
As you can see from the picture, F# has a really good integration with VS2005. Even during the typing F# complains for type consistency, providing warnings and highlighting the wrong code.

In the same class we define the registration functions. They are actually unnecessary since we miss the "register for com interop" project flag available in C#. Supposed that we have such flag the code would be more or less like the following snippet. Anyway, I will do the registration manually later.
(* Add-in Registration functions *)
static member GetSubKeyName (inType :Type ) =
@"CLSID\{" + (inType.GUID.ToString()).ToUpper() + @"}"
[<ComRegisterFunctionAttribute>]
static member RegisterFunction (inType :Type ) =
let clsid = Registry.ClassesRoot.CreateSubKey(AddInServer.GetSubKeyName inType)
clsid.SetValue(null, "F# InventorAddIn")
let mutable subkey = clsid.CreateSubKey(@"Implemented Categories\{39AD2B5C-7A29-11D6-8E0A-0010B541CAA8}")
subkey.Close()
subkey <- clsid.CreateSubKey("Settings")
subkey.SetValue("AddInType", "Standard")
subkey.SetValue("LoadOnStartUp", "1")
subkey.SetValue("SupportedSoftwareVersionGreaterThan", "11..")
subkey.SetValue("Version", 0)
subkey.Close()
subkey <- clsid.CreateSubKey("Description")
subkey.SetValue(null, "InventorAddIn1") |> ignore
[<ComUnregisterFunctionAttribute>]
static member UnregisterFunction (inType :Type ) =
let clssRoot = Microsoft.Win32.Registry.ClassesRoot
let clsid = clssRoot.OpenSubKey(AddInServer.GetSubKeyName inType, true)
clsid.SetValue(null, "")
clsid.DeleteSubKeyTree(@"Implemented Categories\{39AD2B5C-7A29-11D6-8E0A-0010B541CAA8}") // required by Inventor
clsid.DeleteSubKeyTree("Settings")
clsid.DeleteSubKeyTree("Description")
clsid.Close() |> ignore
end // close the classCompile your project: you should find the InventorAddIn.dll in your project folder.
As I mentioned before, I am going to manually register the Add-In. Go back to the Visual Studio 2005 command prompt and move to your project folder. We use the RegAsm tool to create the required REG file.
RegAsm InventorAddIn.dll /codebase /regfile:InventorAddIn.reg
Unfortunately this is not enough. Inventor does not recognize this dll as an Add-in unless you create some additional entries. Open the InventorAddIn.reg file with your favourite editor and add the following lines (bold lines only):
REGEDIT4
[HKEY_CLASSES_ROOT\FSInventorAddIn.AddInServer]
@="FSInventorAddIn.AddInServer"
[HKEY_CLASSES_ROOT\FSInventorAddIn.AddInServer\CLSID]
@="{5E707342-8849-11DC-8314-0800200C9A66}"
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}]
@="FSInventorAddIn.AddInServer"
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\InprocServer32]
@="mscoree.dll"
"ThreadingModel"="Both"
"Class"="FSInventorAddIn.AddInServer"
"Assembly"="inventoraddin, Version=0.0.0.0, Culture=neutral, PublicKeyToken=1dfdf97c76eb5dcd"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///E:/Src/F#/Inventor/InventorAddIn/InventorAddIn.dll"
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\InprocServer32\0.0.0.0]
"Class"="FSInventorAddIn.AddInServer"
"Assembly"="inventoraddin, Version=0.0.0.0, Culture=neutral, PublicKeyToken=1dfdf97c76eb5dcd"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file:///E:/Src/F#/Inventor/InventorAddIn/InventorAddIn.dll"
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\ProgId]
@="FSInventorAddIn.AddInServer"
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\Implemented Categories\{39AD2B5C-7A29-11D6-8E0A-0010B541CAA8}]
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\Settings]
"LoadOnStartUp"="1"
"Type"="Standard"
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\Description]
@="F# Simple Add-In: QS Informatica"
[HKEY_CLASSES_ROOT\CLSID\{5E707342-8849-11DC-8314-0800200C9A66}\Required Categories\{39AD2B5C-7A29-11D6-8E0A-0010B541CAA8}]Probably you will also see the entries created for the Button class. The only remaining thing to do is registering the .reg file. You can do that from the contextual menu or from the command prompt:
regedit /s InventorAddIn.reg
this will add the required keys to the Windows registry..
As I said, F# provides a smooth integration with Visual Studio 2005. This means you can actually debug your F# code in VS2005. Open the project properties and add the required debug information.

Now you are free to place a couple of breakpoints in your code and debug as you normally would do in your C++/CLI, VB.NET or C# programs.

Run the debug session. As soon as Inventor starts you should see the new F# toolbar with the F# button. Press the button and the welcome message should appear.

In case something goes wrong, make sure your Add-in has been loaded. Go to the tools menu and check whether your Add-in is in the list.

You may be curious to know how your F# Add-In looks like. You can use the excellent Lutz Roeder's .NET reflector (http://www.aisto.com/roeder/dotnet/) to dig into your add-in assembly. The structure of the AddIn assembly looks like this:

The required AddIn server class look like this:

I compared the signature of this Add-In with a standard C# Add-In. They look pretty similar but in our Add-in the Activate/Deactivate methods are private whereas in the C# Add-In they are public. I attempted to expose the two methods out of the Inventor.ApplicationAddInServer Interface declaration. Doing this the two methods change to public and the assemblies looks absolutely identical. Despite this Inventor does not like the Add-In and refuses to load it.
The following structure represents our Button class.

In this tutorial I explained the basic steps required to create a .NET Add-In for Autodesk Inventor, using F#. I think this article can be useful to programmers that want to investigate the Inventor Add-In mechanism and to everyone interested in learning F#.
Here are few references on the argument
Inventor 2008 API reference (admapi_12_0.chm) - Add-In Creation
How to create an addin for Inventor using Visual basic .NET (http://www.mcadforums.com/forums/files/addin_creation.doc)
Creating an Inventor Add-In using VB.NET
This tutorial has been created using DOCBOOK.