next previous home

3-4 Change the type of a standard instance

In this section, we change the type of a standard family instance element. We make use of a windows form that displays all available families and types for the category of the single selected instance. Since the lab involves some user interface elements, it may take too long for a hands-on lab. If so, compile and debug the solved lab and discuss the code and its comments with the instructor and your peers. If you are confident with windows forms development, you may try to design similar code in your own project as well. The relevant code in the command does three things:

Before proceeding with the selected instance change, we need to ensure that only a single element is selected. We also need to ensure that the element is of FamilyInstance type.

  public class Lab3_4_ChangeSelectedInstanceType : IExternalCommand
  {
    public IExternalCommand.Result Execute(
      ExternalCommandData commandData,
      ref string message,
      ElementSet elements )
    {
      Application revitApp = commandData.Application;
      Document doc = revitApp.ActiveDocument;
      //
      // Make sure we have a single FamilyInstance selected:
      //
      ElementSet ss = doc.Selection.Elements;
      // First make sure we have a single FamilyInstance selected
      if( 1 != ss.Size )
      {
        LabUtils.ErrorMsg( "Please pre-select a single family instance element." );
        return IExternalCommand.Result.Cancelled;
      }
      ElementSetIterator itTmp = ss.ForwardIterator();
      itTmp.MoveNext();
      Element elTmp = itTmp.Current as Element;
      if( !(elTmp is FamilyInstance) )
      {
        LabUtils.ErrorMsg( "Selected element is NOT a standard family instance." );
        return IExternalCommand.Result.Cancelled;
      }
      FamilyInstance inst = elTmp as FamilyInstance;
      Category instCat = inst.Category;
      //
      // todo:
      //
      // Collect all types applicable to this category and sort them into
      // a dictionary mapping the family name to a list of its types.
      //
      // todo:
      //
      // Display the form, allowing the user to select a family
      // and a type, and assign this type to the instance.
      //
      return IExternalCommand.Result.Succeeded;
    }
  }
' Form-utility to change Type for a selected standard Instance
Public Class Lab3_4_ChangeSelectedInstanceType
    Implements IExternalCommand

    Public Function Execute(ByVal commandData As Autodesk.Revit.ExternalCommandData, ByRef message As String, ByVal elements As Autodesk.Revit.ElementSet) As Autodesk.Revit.IExternalCommand.Result Implements Autodesk.Revit.IExternalCommand.Execute

        Dim revitApp As Revit.Application = commandData.Application
        Dim doc As Revit.Document = revitApp.ActiveDocument

        'First make sure we have a single FamilyInstance selected
        '--------------------------------------------------------
        Dim inst As FamilyInstance
        Dim instCat As Category

        Dim ss As ElementSet = doc.Selection.Elements

        ' First make sure we have a single FamilyInstance selected
        If Not ss.Size = 1 Then
            MsgBox("You must pre-select a single element!")
            Return IExternalCommand.Result.Cancelled
        Else
            Dim itTmp As ElementSetIterator = ss.ForwardIterator
            itTmp.MoveNext()
            Dim elTmp As Revit.Element = itTmp.Current
            If Not TypeOf elTmp Is FamilyInstance Then
                MsgBox("Selected element is NOT a standard family instance!")
                Return IExternalCommand.Result.Cancelled
            Else
                inst = elTmp
                instCat = inst.Category
            End If
        End If
        '
        ' todo:
        '
        ' Collect all types applicable to this category and sort them into
        ' a dictionary mapping the family name to a list of its types.
        '

        '
        ' todo:
        '
        ' Display the form, allowing the user to select a family
        ' and a type, and assign this type to the instance.
        '
        Return IExternalCommand.Result.Succeeded
    End Function
End Class

To create the dictionary of relevant families with a list of all the symbols of each, we can use the new filtering mechanism to create a type filter (of Family type). With this set of family elements, we will match the category of the family (or the symbol, as the case might be) with that of the selected family instance element. It is a good practice to check for category match against category id instead of category name (and more so while comparing with built-in category). The following code illustrates the approach described above:

  Dictionary<string, List<FamilySymbol>> dictFamilyToSymbols = new Dictionary<string, List<FamilySymbol>>();
  {
    WaitCursor waitCursor = new WaitCursor();
    //
    // We create a collection of all loaded families for this category
    // and for each one, the list of all loaded types (symbols).
    //
    // There are many ways how to store the matching objects, but we choose whatever is most suitable for the relevant UI:
    // We could use Revit's generic Map class, but it's probably more efficient to use the new 2005 .NET strongly-typed Dictionary with
    // KEY = Family name (String)
    // VALUE = ArrayList (implements iList so we can elegantly bind it to combobox) of corresponding FamilySymbol obects
    //
    // Find all the corresponding Families/Types:
      List<Element> families = new List<Element>();
      Filter filterFamily = app.Create.Filter.NewTypeFilter( typeof( Family ) );
      doc.get_Elements( filterFamily, families );
      foreach( Family f in families )
      {
        bool categoryMatches = false;
        if( null == f.FamilyCategory )
        {
          foreach( FamilySymbol sym in f.Symbols )
          {
            categoryMatches = sym.Category.Id.Equals( instCat.Id );
            break;
          }
        }
        else
        {
          categoryMatches = f.FamilyCategory.Id.Equals( instCat.Id );
        }
        if( categoryMatches )
        {
          List<FamilySymbol> familySymbols = new List<FamilySymbol>();
          foreach( FamilySymbol sym in f.Symbols )
          {
            familySymbols.Add( sym );
          }
          dictFamilyToSymbols.Add( f.Name, familySymbols );
        }
      }
    }
    '
    ' We create a collection of all loaded families for this category
    ' and for each one, the list of all loaded types (symbols).
    '
    ' There are many ways how to store the matching objects, but we choose whatever is most suitable for the relevant UI:
    '   We could use Revit's generic Map class, but it's probably more efficient to use the new 2005 .NET strongly-typed Dictionary with
    '   KEY = Family name (String)
    '   VALUE = ArrayList (implements iList so we can elegantly bind it to combobox) of corresponding FamilySymbol obects
    Dim dictFamilyToSymbols As New Dictionary(Of String, ArrayList)

    ' Looping may take a few seconds, so let user know by changing the cursor
    Dim oldCursor As Cursor = Cursor.Current
    Cursor.Current = Cursors.WaitCursor

    ' using Revit 2009 element filtering
    Dim families As List(Of Revit.Element) = New List(Of Revit.Element)
    Dim filterFamily As Revit.Filter = commandData.Application.Create.Filter.NewTypeFilter(GetType(Family))

    Dim nRetVal = doc.Elements(filterFamily, families)
    Dim f As Family
    Dim categoryMatches = False
    For Each f In families
        categoryMatches = False
        If (f.FamilyCategory Is Nothing) Then
            For Each sym As FamilySymbol In f.Symbols
                categoryMatches = sym.Category.Id.Equals(instCat.Id)
                Exit For
            Next
        Else
            categoryMatches = f.FamilyCategory.Id.Equals(instCat.Id)
        End If

        If (categoryMatches) Then
            Dim familySymbols As New ArrayList
            For Each sym As FamilySymbol In f.Symbols
                familySymbols.Add(sym)
            Next
            dictFamilyToSymbols.Add(f.Name, familySymbols)
        End If

    Next

Displaying the form and assigning the selected type is short and sweet:

  //
  // Display the form, allowing the user to select a family
  // and a type, and assign this type to the instance.
  //
  Lab3_4_Form frm = new Lab3_4_Form( dictFamilyToSymbols );
  if( WinForms.DialogResult.OK == frm.ShowDialog() )
  {
    try
    {
      inst.Symbol = frm.cmbType.SelectedItem as FamilySymbol;
      LabUtils.InfoMsg( "Successfully changed Family:Type to " + frm.cmbFamily.Text + " : " + frm.cmbType.Text );
    }
    catch( Exception )
    {
    }
  }
    '
    ' Display the form, allowing the user to select a family
    ' and a type, and assign this type to the instance.
    '
    Dim frm As New Lab3_4_Form(dictFamilyToSymbols)
    Cursor.Current = oldCursor ' restore cursor
    If frm.ShowDialog = Windows.Forms.DialogResult.OK Then
        Try
            inst.Symbol = frm.cmbType.SelectedItem
            MsgBox("Successfully changed Family:Type to " & frm.cmbFamily.Text & " : " & frm.cmbType.Text)
        Catch
        End Try
    End If

Compile and link the project and update the Revit.ini file accordingly. Debug the code and discuss with the course instructor and your peers.

next previous home copyright © 2007-2008 jeremy tammik, autodesk inc. all rights reserved.