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 app = commandData.Application; Document doc = app.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 app As Revit.Application = commandData.Application Dim doc As Revit.Document = app.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 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-2009 jeremy tammik, autodesk inc. all rights reserved.