Wednesday, January 19, 2011

Batch Family Fun for Everyone!!! Part 1

I bet you've been waking up recently in the middle of the night dreaming about how you could jockey the Revit API to batch process families, haven't you?... Well you're in luck because that's exactly what this post is for.

There's obviously a whole load of topics to dive into on batch processing families, so to keep it short and sweet I'll touch on how to open a family, add a shared parameter with a set value and close the family with the saved values. Later posts will build on even cooler family API processing ideas.

The first thing you will need to accomplish is get a reference to the local Revit application and document using a class that implements the IExternalCommand interface. The code belows shows the beginning of such a class...


Public Class ScribblesForBlog

    Private m_Manager As FamilyManager = Nothing
    Private m_App As UIApplication
    Private m_Doc As Document = Nothing
    Private m_SharedParamFile As DefinitionFile

    ''' <summary>
    ''' General Class Constructor
    ''' </summary>
    ''' <param name="CmdData"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal CmdData As ExternalCommandData)
        m_Doc = CmdData.Application.ActiveUIDocument.Document
        m_App = CmdData.Application
        If m_Doc.IsFamilyDocument Then m_Manager = m_Doc.FamilyManager
        ' Get a reference to the active shared parameter file if one exists
        Try
            m_SharedParamFile = m_App.Application.OpenSharedParameterFile
        Catch ex As Exception
            ' It is possible to have an empty value here (No Active Shared Parameter File)
        End Try
    End Sub

End Class

The next thing we need is a quick and simple means of opening and closing external family files by calling their file paths as an argument to open the file. The functions below will open and close the active family respectively:


''' <summary>
    ''' Open an external family file and set the active doc reference to this family
    ''' </summary>
    ''' <param name="m_FileName">Full path to a valid rfa file</param>
    ''' <returns>Set the document object to this family on success</returns>
    ''' <remarks>www.RevitNet.blogspot.com RULES</remarks>
    Public Function OpenExternalFamily(ByVal m_FileName As String) As Boolean
        Try
            ' Open the family and set the active document object to the family
            m_Doc = m_App.Application.OpenDocumentFile(m_FileName)

            ' Verify the active document is the correct family (paranoia)
            If m_Doc.IsFamilyDocument And m_Doc.PathName.ToUpper = m_FileName.ToUpper Then
                ' Update the reference to the active family manager
                m_Manager = m_Doc.FamilyManager
                ' We're done, success
                Return True
            End If
            ' If we got to this line then something failed
            m_Manager = Nothing
            Return False
        Catch ex As Exception
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Close the current family file
    ''' </summary>
    ''' <param name="p_SaveFile">Set to True will save the file on close</param>
    ''' <returns>Optional save, default is YES</returns>
    ''' <remarks></remarks>
    Public Function CloseActiveFamily(Optional ByVal p_SaveFile As Boolean = True) As Boolean
        Try
            ' This should only run on family files
            If m_Doc.IsFamilyDocument Then
                ' True means to save the file 
                m_Doc.Close(p_SaveFile)
            End If
            ' We're done, success
            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function

Now that we can open and close the families... lets add a shared parameter! That would be exciting, right? We can open a shared parameter using:


''' <summary>
    ''' Open as set the shared parameter file
    ''' </summary>
    ''' <param name="p_FileName">Full path to the shared parameter file</param>
    ''' <remarks></remarks>
    Public Sub OpenSharedParameterFile(ByVal p_FileName As String)
        If IO.File.Exists(p_FileName) Then
            Try
                m_App.Application.SharedParametersFilename = p_FileName
                m_SharedParamFile = m_App.Application.OpenSharedParameterFile
            Catch ex As Exception
                ' Should never fail... maybe a poorly formatted shared parameter file?
            End Try
        End If
    End Sub

And now for some real excitement! The code below will search your active shared parameter file for a specific parameter by name and then add it to the family and set a value to it!:


''' <summary>
    ''' Add a shared parameter to the current family and use a string
    ''' to set its value. A double formatted parameter can use 9'-4" (architectural) format
    ''' </summary>
    ''' <param name="SharedParameterName">The Name of the parameter to find and add</param>
    ''' <returns>True on Success</returns>
    ''' <remarks></remarks>
    Public Function AddSharedParameter(ByVal SharedParameterName As String, _
                                       ByVal SharedParameterValue As String, _
                                       ByVal isInstParameter As Boolean) As Boolean
        Try
            ' Make sure we are working with a valid family document
            If m_Doc.IsFamilyDocument Then
                ' make sure we have a valid shared parameter file
                If m_SharedParamFile IsNot Nothing Then
                    ' Iterate the Parameter Groups
                    For Each group As DefinitionGroup In m_SharedParamFile.Groups
                        ' Iterate the Parameters in the Group
                        For Each def As ExternalDefinition In group.Definitions
                            ' Do we have a match?
                            If def.Name.ToUpper <> SharedParameterName.ToUpper Then Continue For
                            ' We found the parameter that we're after, 
                            ' does it already exist in the family?
                            Dim param As FamilyParameter = m_Manager.Parameter(def.Name)
                            ' If we have a valid parameter object,
                            ' then it exists in the family (no need to add it)
                            If param IsNot Nothing Then
                                ' Start a new Family transaction
                                Dim m_TransFam As New Transaction(m_Doc, "Family Transaction")
                                m_TransFam.Start()
                                Try
                                    ' Set the value from string...
                                    '9'-6" can set to a double or a string
                                    m_Manager.SetValueString(param, SharedParameterValue)
                                    m_TransFam.Commit()
                                Catch ex As Exception
                                    ' Roll back on failure
                                    m_TransFam.RollBack()
                                End Try

                            Else ' Need to add the parameter...
                                ' Start a new Family transaction
                                Dim m_TransFam As New Transaction(m_Doc, "Family Transaction")
                                ' Add the parameter as type or instance
                                If param.IsInstance = isInstParameter Then
                                    m_TransFam.Start()
                                    Try ' First add the parameter
                                        m_Manager.AddParameter(def, def.ParameterGroup, isInstParameter)
                                        Dim fParam As FamilyParameter = m_Manager.Parameter(def.Name)
                                        If fParam IsNot Nothing Then
                                            ' Set the value from string...
                                            '9'-6" can set to a double or a string
                                            m_Manager.SetValueString(param, SharedParameterValue)
                                        End If
                                        m_TransFam.Commit()
                                    Catch ex As Exception
                                        ' Roll back on failure
                                        m_TransFam.RollBack()
                                    End Try

                                End If
                            End If
                        Next
                    Next
                End If
            End If
            Return True
        Catch ex As Exception
            ' Something did not go according to plan if we make it here
            Return False
        End Try
    End Function

So that's the basics! You now know how to open a family and add a shared parameter to it... Look for more batch family capabilities in future posts!