Tuesday, 23 December 2008

Opening your current working directory

I love simple ideas that can save time and hassle here and there. Here's one of them. I got fed up of navigating to my current working directory so built a shortcut to open it using the following:

Dim activeDoc As Document = revitApp.ActiveDocument

If activeDoc.PathName <> "" Then
Process.Start("explorer.exe", Replace(activeDoc.PathName, activeDoc.Title, ""))
End If
Just drop this into a class implementing IExternalCommand, call your dll from a menu item or button of your making, and hey presto life is soooo much better. One click and you're there.

My tabbing ain't working

If you're opening your forms like this:


then you're opening a modeless dialog, and may experience some strange behaviour. The most prominent of these is the loss of the tab button - your users won't be able to tab through controls on the form.

To overcome this, you need to use this:


which opens a modal dialog, and all expected functionality is available. But then your users can't interact with Revit until it is closed.

So I'm faced with a bit of a trade-off. Some of my forms are modal, and those that require the user to interact with Revit are modeless, which means that tabbing and other functionality (the Delete key is another) is lost. I'm lucky in that it hasn't caused too many problems, but I wonder how many programmers find this a real pain?

Friday, 12 December 2008

More on 64

I raised an ADN support query about 32/64 bit issues and I had a promising response suggesting that there shouldn't be a problem running plugins on the two different (32-bit and 64-bit) Revit platforms, so I did a little experiment.

I built a simple plugin that uses the API to simply report the current Revit version - just a way of confirming things are working or not. In Visual Studio I built my assembly three different times and set my Target CPU (Properties > Compile > Advanced Compile Options) to 'Any CPU', 'x86', and 'x64' respectively. 

This is what I found:

Any CPU - runs fine in both Revit 32 and 64
x64 - only runs in Revit 64
x86 - only runs in Revit 32

This confirms one of my earlier posts, but is reassuring for most plugin developers as using 'Any CPU' means you don't have to worry about separate releases. However some developers have to use x86 as their Target CPU because they use 32-bit only components in their code, such as the OLE DB provider for Microsoft Jet (for Access databases), which has no 64-bit version.

Time for a coffee methinks...

Add-in Manager tip

I noticed today that the Revit Add-in Manager has a 'Run' button, which at least gives me a little bit of feedback when one of my plugins fails to load.  The feedback I'm seeing at the moment is "Unable to run the command class".

I don't know why its failed, but this titbit of information is better than nothing, which is exactly what you get if you go via Tools > External Tools.

Tuesday, 9 December 2008

64 bit can of worms

Autodesk very recently released Revit Architecture 2009 64-Bit, and us Revit programmers need to consider how this affects our plugins. 

64-bit platforms support 32-bit processes by using the WOW64 (Windows on Windows) subsystem, which means your code will have worked on 64-bit platforms when running Revit 32. But if your plugin is 32-bit it cannot be used by Revit 64, because 64-bit processes cannot call 32-bit modules into their processing space.

Now, for a lot of smaller/simpler plugins the solution will be quite simple. Change a few settings in your Visual Studio project, and recompile as a 64-bit process.

But for others it might not be quite so simple. If like me you're relying on components that can't be accessed in a 64-bit environment then you have some trouble ahead. Such a component, and a common one too, is the Microsoft Jet Engine database. If you're using this (you may know it as Access) with your Revit plugin then you're going to be incompatible with Revit 64 until you either build a complicated proxy-type process, or start using something else like SQL Server Compact.

I'll be looking into this a little more very soon. Watch this space.

Thursday, 4 December 2008

Finding Filters

I needed to get some information on Filters setup by users. It wasn't immediately apparent how or whether I could do this with the API, so I used a technique that I have used frequently in such circumstances.

Create an empty Revit model, and use the API to list all elements in the model. Save this list, and then add to your model the element/item/object/feature you're interested in - in this case a filter. Now once again produce a full element list using the API.

The difference between your second list and your first list will enable you to identify your element by id. Then, armed with this information you can delve further by listing its parameters.

For a filter I found this:

ELEM_CATEGORY_PARAM_MT Category ElementId read-only -1
ELEM_CATEGORY_PARAM Category ElementId read-only -1
DESIGN_OPTION_ID Design Option ElementId read-only -1
PHASE_DEMOLISHED Phase Demolished ElementId read-write -1
PHASE_CREATED Phase Created ElementId read-write -1
ELEMENT_LOCKED_PARAM Locked Integer read-write 0
ELEM_DELETABLE_IN_FAMILY Deletable Integer read-write 1
UNIFORMAT_DESCRIPTION Assembly Description String read-only
UNIFORMAT_CODE Assembly Code String read-write
ID_PARAM Id ElementId read-only Symbol 'Filter 1' 123245
EDITED_BY Edited by String read-only
ELEM_PARTITION_PARAM Workset Integer read-write 0
ELEM_FAMILY_AND_TYPE_PARAM Family and Type ElementId read-only -1
ELEM_FAMILY_PARAM Family ElementId read-only -1
ELEM_TYPE_PARAM Type ElementId read-only -1
SYMBOL_FAMILY_AND_TYPE_NAMES_PARAM Family and Type String read-only Filters: Filter 1
SYMBOL_FAMILY_NAME_PARAM Family Name String read-only Filters
SYMBOL_FAMILY_NAME_PARAM Family Name String read-only Filters
SYMBOL_NAME_PARAM Type Name String read-only Filter 1
SYMBOL_NAME_PARAM Type Name String read-only Filter 1
SYMBOL_ID_PARAM Type Id ElementId read-only -1

So, to identify filters in your model you can use the SYMBOL_FAMILY_NAME_PARAM like below. Iterate through all elements, check the SYMBOL_FAMILY_NAME_PARAM value, and where it is 'Filters', then you have found a filter:

Dim elementIterator As Autodesk.Revit.ElementIterator
elementIterator = revitApp.ActiveDocument.Elements

While (elementIterator.MoveNext())

'filters are a symbol, so start here,
'rather than paramcheck every element
If TypeOf elementIterator.Current Is Autodesk.Revit.Symbol Then

Dim symbol As Autodesk.Revit.Symbol = elementIterator.Current

' then test SYMBOL_FAMILY_NAME_PARAM for 'Filters'.
If ParameterChecker(symbol, "testCondition", "SYMBOL_FAMILY_NAME_PARAM", "Filters") Then
'we have found a filter!
End If

End If
End While

The function ParameterChecker looks like below. I adapted this from the Revit API intro to perform three different functions. 'testCondition' tests a named paramater for a specified value and returns true or false. 'getValue' returns the value of a named parameter. 'fullReport' lists all parameters and values for a named element.

Public Shared Function ParameterChecker(ByVal elem As Autodesk.Revit.Element, ByVal task As String, ByVal paramName As String, Optional ByVal value As String = "")
Dim _ParamEnums As New ArrayList
Dim _ParamTypes As New ArrayList
Dim _ParamValues As New ArrayList

' Takes some time, so change cursor
Dim oldCursor As Cursor = Cursor.Current
Cursor.Current = Cursors.WaitCursor

Dim fis() As System.Reflection.FieldInfo = GetType(BuiltInParameter).GetFields
For Each fi As System.Reflection.FieldInfo In fis
' See if this is an enum (a literal value set at compile time)
If fi.IsLiteral Then
Dim enumInt As Integer = CType(fi.GetValue(Nothing), Integer)
Dim enumBip As BuiltInParameter = enumInt
Dim param As Parameter = elem.Parameter(enumBip)
If Not (param Is Nothing) Then ' this check is much faster than throwing an exception for each invalid param!
Select Case param.StorageType

Case StorageType.Double

Case StorageType.Integer

Case StorageType.String

Case StorageType.ElementId

Case StorageType.None
' nothing
Case Else
' nothing
End Select
End If
Catch ex As Exception
End Try
End If 'isLiteral
Next fi ' looping field infos

' Revert the cursor
Cursor.Current = oldCursor

'KIS for now - in future may display in an user-friendly form...
Dim msg As String = "Number of valid Params  = " & _ParamEnums.Count & ", " & _ParamTypes.Count & ", " & _ParamValues.Count

msg = "Valid Params for this element: "
Dim iNum As Integer = _ParamValues.Count
For i As Integer = 0 To iNum - 1
msg += vbCrLf & "  " & _ParamEnums(i) & ", " & _ParamTypes(i) & ": " & _ParamValues(i)
If task = "testCondition" Then
If _ParamEnums(i) = paramName And _ParamValues(i) = value Then
Return True
End If
ElseIf task = "getValue" Then
If _ParamEnums(i) = paramName Then
Return _ParamValues(i)
End If

ElseIf task = "fullReport" Then
WriteOutput(vbCrLf & "  " & _ParamEnums(i) & ", " & _ParamTypes(i) & ": " & _ParamValues(i))

End If


End Function

So this is how you might identify if a model has filters applied, and you can get the name of the filter too. But for me this isn't enough, as I want to know what categories are included and what its rules are.

Its now on the API wish-list as PR #154826 [API wish: API access to filters in Revit model] :)

Tuesday, 2 December 2008

Revit Add-In Manager

I should have mentioned in yesterday's blog entry the Add-In Manager for Revit. This neat little tool solves all your ini editing woes and makes it a doddle to add or remove Revit Add-Ins. The irony is that its an Add-In itself and should really be a part of the core software.

Its so handy that I wanted to point my customers to a download for it to help them install our app. The problem is, this 1.5MB tool only comes as part of the Revit SDK, which users either have to find on their original Revit disk or download all 55MB of it from the interweb. And when you've unzippedand installed the SDK you have to do a bit of digging to actually find the thing.

I asked Autodesk if I was allowed to distribute it myself, but unsurprisingly the response from Jeremy Tammik was technically "yes", but legally "no". Anyway, if you need it just ask me, and I'll "guide you to it" if you know what I mean.

Monday, 1 December 2008

The Revit.ini

There's nothing new in ranting about the Revit.ini. I've read plenty of forum and blog entries putting the boot into what is, lets face it, a bit of antiquated technology.

This rant isn't about the fact that the ini exists, but about the way it works. As a developer of third party software for Revit I want my customers to have a pain-free installation, as any pain for them is pain for me as I have to help them sort it out. They might also be potential customers for whom first impressions are important, and while the need for support at this point in a customer-vendor relationship is a great opportunity for us to show just how good our support is, I think we'd all rather it was never needed in the first place.

As you've probably gathered, editing the Revit.ini to add an external command is a process fraught with potential pitfalls if you're the type not to read a README.txt no matter how many times you've been told. In the readme file distributed with our app I would love to be able to simply provide the following lines of code that the customer can copy and paste into their ini:

ECName1=My Amazing Add-in
ECAssembly1=C:\Program Files\MyCompany\MyamazingAddin\MyAddin.dll
ECDescription1=This is a great add-in

Unfortunately I can't, because if you already have an add-in then the process becomes a little cloudy. You have to add your new lines under the existing [ExternalCommands] heading, under the existing ECCount entry, and under any existing external command entries you have. With each new add-in you have to increment the ECCount, and then alter your new entries accordingly (ECNameX, ECClassNameX, ECAssemblyX, ECDescriptionX, where X = ECCount).

Contrived huh?

Well its easy for me to say, but why on Earth the developers insist that the user does the counting I will never know. It would be far nicer for us to be able to simply do this for each EC:

ECName=My Amazing Add-in
ECAssembly=C:\Program Files\MyCompany\MyamazingAddin\MyAddin.dll
ECDescription=This is a great add-in

and then during startup Revit could count the External Commands. Hindsight is a wonderful thing.

One more thing - did you know you can comment out lines using the apostrophe like this:

'ECAssembly1=C:\Program Files\MyCompany\MyamazingAddin\MyAddin.dll

I use this quite often to quickly switch between an installed version of my app and one in development.

Thursday, 27 November 2008

Accessing schedules with the Revit API

I appreciate you may have excitedly arrived here from a successful search on Google, wondering how you use the Revit API with schedules. Well I'm afraid its bad news. You can't.

In the new CADsmart Revit skills assessment we wanted to assess a user's ability to create schedules as specified, but met a dead end with a support request on the ADN confirming that with schedules most commands are disabled, and that there is no API that allows access to any cell values in a schedule.

I'm hoping that this might be resolved with the next release. For the moment our assessment asks the user to export the schedule to a delimited text file so our code can access it, which is perfectly legitimate, but a step I'd rather do without.

Copy versus Array

It came to my attention recently that two different methods of placing an object in what should be the same places actually might end in slightly different results.

I'll explain by way of example. I had an office space into which I imported a .dwg of a cluster of desks. This instance was to be copied 3 further times and the copies were to be placed 3000mm apart, like this:

I did it the slightly laborious way by copying and moving. My clearly much cleverer colleague went for the more efficient array option. When I came to analyze the files using the BoundingBox property I discovered that what we ended up with were ever so slightly differently placed instances. And by ever so slightly I mean a different value in the 11th decimal place.

OK so perhaps I'm picking at straws here, but it had a serious impact on the reliability of what I was trying to do, which was to extract data as strings from two files to two databases, and compare this data looking for precise matches.

My solution was to use 


throughout, as I realised that such accuracy simply isn't required for my purposes.

But why does Revit do this? My guess is its simply a by-product of rounding in Revit's array code, or cumulative rounding from my clumsier but accurate moving and copying. I haven't delved any further, but I would hope if its the former that its not cumulative with the size of an array, as this would mean greater inaccuracies the more objects that are arrayed.

Monday, 24 November 2008

Opening a file in Revit

A simple place to start!

A fundamental requirement of some of the software I build is to open Revit files, present them to the user, ask the user to do some things to it, save and close the file, and then open another. Trying to open a Revit file is where I started many months back and boy was I disappointed; I wondered at this point if the API was going to be able to meet any of our needs. Perhaps my expectations were too high having worked with the mature AutoCAD API.

You can indeed open existing Revit files using the following:


But this doesn't present the file to the user. You can't see it or access it, the file is waiting to be altered programatically, not by a human.

Apparently this functionality is on the wish-list, having been requested multiple times, so until then if you need to open a file for a user, you need to 'botch' it by asking Windows to open it:


Assuming the host machine knows what to do with a .rvt file, then your file will open up into Revit. And then you can use ActiveDocument to manipulate/save/close etc.