Thursday, 26 March 2009

Parameters in nested families

I was asked if I knew how to access parameters in a nested family using the API, and was sent some sample files. It turned out be more of a Revit user issue than a programmer issue, IMO.

I had to edit the nested family, open up the 'Family Category and Parameters' dialog, and check 'Shared' in the 'Family Parameters' window:

Then I had to reload it into the host family, and reload the host family into my project.

Then using this code, the parameters could be accessed:

Public Shared Function ExtractAll()

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


While (elementIterator.MoveNext())
Dim currentElm As Autodesk.Revit.Element
currentElm = elementIterator.Current

WriteOutput("Id = " & currentElm.Id.Value)
WriteOutput("Name = " & currentElm.Name)
WriteOutput("Type = " & elementIterator.Current.GetType.Name)

'go get the parameters

End While

Return True

Catch ex As Exception
Return False
End Try

End Function

Public Shared Function ParameterCheckerNew(ByVal elem As Autodesk.Revit.Element)
Dim params As ParameterSetIterator = elem.Parameters.GetEnumerator

While params.MoveNext
Dim currentParam As Parameter
currentParam = params.Current

Select Case currentParam.StorageType

Case StorageType.Double

Case StorageType.Integer

Case StorageType.String

Case StorageType.ElementId

Case StorageType.None
' nothing
Case Else
' nothing
End Select

End While

End Function

Note above I'm simply looping through every element in the file. This is lazy coding, and you could create any kind of filter you want here to see only the elements you need.

Interestingly I couldn't seem to access it using a selection iterator, as only the host family is recognised as selected, not the nested family within. There's probably a way of drilling down into a selected family and seeing what's nested in it, but I'll take a look at that some other time.

Tuesday, 17 March 2009

Modifying a user's Revit.ini file

Historically I've been a bit cautious about this, as it just feels wrong sneaking into another Program's installation directory and modifying any file, let alone one with a .ini extension. But leaving my users having to edit their ini manually ad infinitum wasn't an acceptable long-term solution.

It was to my pleasant surprise that the SDK contains a sample of C# code designed to modify the Revit.ini. And very simple it looks too. It makes use of the windows registry functions WritePrivateProfileString and GetPrivateProfileInt.

Here I've knocked up a version and added one feature I wouldn't feel safe without - a backup copy in case it all goes wrong! Make a console application that starts in Sub Main, with two vb modules called anything you like. Drop this code into one module:

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.IO

Module General

    Public Sub Main(ByVal args() As String)
            ' path to Revit.ini
            Dim iniFilename As String = "C:\Path to Revit installation\Revit.ini"

            ' check exists
            If File.Exists(iniFilename) Then
                'make backup in case anything goes wrong!
                FileCopy(iniFilename, iniFilename & ".BAK")

                Console.WriteLine("File ""{0}"" not found.", iniFilename)
                Throw New ApplicationException("Ini file not found")
            End If

            Dim externalCommands As String = "ExternalCommands"
            Dim commandName As String = "This is my command name"
            Dim className As String = ""
            Dim commandDescription As String = "Whatever you like"

            Dim revitIni As New IniFile(iniFilename)

            ' get count if exists.  else 0
            Dim ecCount As Integer = revitIni.GetIniValue(externalCommands, "ECCount", 0)

            ' increment count
            ecCount += 1
                ' get writing
                revitIni.WriteIniString(externalCommands, "ECCount", ecCount.ToString())
                revitIni.WriteIniString(externalCommands, "ECName" & ecCount.ToString(), commandName)
                revitIni.WriteIniString(externalCommands, "ECClassName" & ecCount.ToString(), className)
                revitIni.WriteIniString(externalCommands, "ECDescription" & ecCount.ToString(), commandDescription)

            Catch ex As Exception
                Console.WriteLine("Write failed")
            End Try

        Catch ex As Exception
            Console.WriteLine("Add External Command failed.")
        End Try

    End Sub

End Module

and drop this into the other:
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Runtime.InteropServices

Public Class IniFile
    'path of ini file
    Private iniPath As String

    ''' Gets or sets the path of ini file
    Public Property Path() As String
            Return iniPath
        End Get
        Set(ByVal value As String)
            iniPath = value
        End Set
    End Property

    <DllImport("kernel32")> _
    Private Shared Function WritePrivateProfileString(ByVal section As String, ByVal key As String, ByVal val As String, ByVal filePath As String) As Integer
    End Function

    <DllImport("kernel32")> _
    Private Shared Function GetPrivateProfileInt(ByVal section As String, ByVal key As String, ByVal def As Integer, ByVal filePath As String) As Integer
    End Function

    Public Sub New(ByVal iniFilePath As String)
        iniPath = iniFilePath
    End Sub

    Public Function WriteIniString(ByVal region As String, ByVal key As String, ByVal value As String) As Boolean
        Return If(WritePrivateProfileString(region, key, value, Me.iniPath) = 0, False, True)
    End Function

    Public Function GetIniValue(ByVal region As String, ByVal key As String, ByVal def As Integer) As Integer
        Return GetPrivateProfileInt(region, key, def, Me.iniPath)
    End Function
End Class

Start her up, and assuming the path to your Revit.ini file is correct it should work. I'll be looking to run this .exe in our installation process so we can banish manual ini editing forever. Yay!

Thursday, 12 March 2009

Images in your Revit 2010 ribbons

Further to my recent post about Ribbons, you can add a 32 x 32 pixel image to your panel button using this code:

myPushButton.LargeImage = New BitmapImage(New Uri("C:\File Path\myImage.bmp", UriKind.Absolute))

And you'll need this too:

Imports System.Windows.Media.Imaging

And here's what it looks like using a picture of a bit of my Lambretta:

If you use a bigger image then it doesn't seem to get resized, Revit just shows the top-left 32 x 32 square. What's particularly interesting is that you can call your images from the web, like this:

myPushButton.LargeImage = New BitmapImage(New Uri("", UriKind.Absolute))

Notice it doesn't have to be a bitmap. You can use gif, png etc, and if you're a whizz with photoshop you can specify transparent colours. I did try an animated gif and to my relief it didn't work. Imagine having a 'spinny logo' vying for your attention while you're trying to do some work!

The little things

Rod recently highlighted one of the smaller and very welcome improvements in Revit 2010 - user feedback when Revit fails to load an External App or Command. Previously there was an ominous silence and the user was left somewhat in the dark.

Further to this, Revit no longer silently renumbers the Revit.ini file entries when they fail to load or start.

This was such a pain, good riddance I say!

Tuesday, 10 March 2009

Ribbons in Revit 2010

Revit 2010 due to be released next month sees the introduction of ribbons to the interface, as first popularised in Microsoft Office 2007, then adopted by Autodesk for AutoCAD 2009.

In a recent post I put up a bit of code showing a simple menu and toolbar for Revit 2009, but finally I've overcome some of my installation problems and had chance to have a play with said ribbons in the forthcoming release.

All add-ins appear under the top-level 'Add-in' tab. External tools appear under the push-button menu 'External Tools' in the 'External' panel, and you can add panels to this ribbon like so:

And here's the code to do this:

Public Class RibbonSample
   Implements IExternalApplication

   Public Function OnStartup(ByVal application As Autodesk.Revit.ControlledApplication) As  _
   Autodesk.Revit.IExternalApplication.Result Implements Autodesk.Revit.IExternalApplication.OnStartup

           'push button
           Dim ribbonPanelButtons As RibbonPanel = application.CreateRibbonPanel("Panel name")
           Dim pushButtonOpenWD As PushButton = ribbonPanelButtons.AddPushButton("Tooltip Bold Title", "Button Name", "C:\pathToMy\coolfunction.dll", "className")
            pushButtonOpenWD.ToolTip = "Tooltip description"

           Return IExternalApplication.Result.Succeeded
       Catch ex As Exception
           MessageBox.Show("Ribbons Failed")
           Return IExternalApplication.Result.Failed
       End Try
   End Function

  Public Function OnShutdown(ByVal application As Autodesk.Revit.ControlledApplication) As  _
  Autodesk.Revit.IExternalApplication.Result Implements Autodesk.Revit.IExternalApplication.OnShutdown

       Return Autodesk.Revit.IExternalApplication.Result.Succeeded

   End Function

End Class

When (or if!) I find the time I'll have a go at adding bitmap images and drop-down menus to my buttons.