Files
RevitSdkSamples/snapshot/2014/bc/BuildingCoder/BuildingCoder/CmdNestedFamilies.cs
T
2024-12-12 09:51:40 +01:00

485 lines
18 KiB
C#

#region Header
//
// CmdNestedFamilies.cs - list nested family files and instances in a family document
//
// Copyright (C) 2010-2013 by Jeremy Tammik, Autodesk Inc. All rights reserved.
//
#endregion // Header
#region Namespaces
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
#endregion // Namespaces
namespace BuildingCoder
{
/// <summary>
/// This class contains functions for dealing with
/// nested families within a Revit family document
/// </summary>
public class NestedFamilyFunctions
{
#region Public Methods
/// <summary>
/// Returns a list of the nested family files in the
/// given family document whose name matches the given
/// family file name filter. Useful for checking to
/// see if a family desired for nesting into the host
/// family document is already nested in.
/// Filtering is done with a simple Contains (substring)
/// check, so wildcards don't work.
/// </summary>
/// <param name="familyFileNameFilter">The portion of the family file loaded into the family document</param>
/// <param name="familyDocument">The family document being queried</param>
/// <param name="caseSensitiveFiltering">Whether or not the filter checking is case-sensitive</param>
/// <example>
/// GetFilteredNestedFamilyFiles("window", document, false);
/// </example>
/// <remarks>
/// Because standard Revit filtering techniques fail when searching for nested families in a
/// family document, we have no choice but to iterate over all elements in the family.
/// While there usually aren't that many elements at the family level, nonetheless this method
/// has been built for speed.
/// </remarks>
/// <returns>
/// A collection of family file definitions nested into the given family document.
/// </returns>
public static IEnumerable<Family>
GetFilteredNestedFamilyDefinitions(
string familyFileNameFilter,
Document familyDocument,
bool caseSensitiveFiltering )
{
// Following good SOA practices, verify the
// incoming data can be worked with.
ValidateFamilyDocument( familyDocument ); // Throws an exception if not a family doc
// The filter can be null, the filter matching function checks for that.
#if _2010
List<Family> oResult = new List<Family>();
ElementIterator it = familyDocument.Elements;
while( it.MoveNext() )
{
Element oElement = it.Current as Element;
if( ( oElement is Family )
&& FilterMatches( oElement.Name,
familyFileNameFilter, caseSensitiveFiltering ) )
{
oResult.Add( oElement as Family );
}
}
#endif // _2010
FilteredElementCollector collector
= new FilteredElementCollector( familyDocument );
collector.OfClass( typeof( Family ) );
IEnumerable<Element> familiesMatching =
from f in collector
where FilterMatches( f.Name, familyFileNameFilter, caseSensitiveFiltering )
select f;
return familiesMatching.Cast<Family>();
}
/// <summary>
/// Returns a list of family instances found in the given family document whose family file
/// name matches the given familyFileNameFilter and whose type name matches the given
/// typeNameFilter. If no filter values are provided (or they evaluate to the empty string
/// when trimmed) then all instances will be evaluated.
/// Filtering is done with a simple Contains (substring) check, so wildcards don't work.
/// </summary>
/// <param name="familyFileNameFilter">The portion of the nested family file name (or exact match) to find</param>
/// <param name="typeNameFilter">The portion of the type name (or exact match) to find</param>
/// <param name="familyDocument">The family document to search.</param>
/// <param name="caseSensitiveFiltering">Whether or not the filter checking is case-sensitive</param>
/// <example>
/// GetFilteredNestedFamilyInstances("window", "double-hung", document, false);
/// </example>
/// <remarks>
/// Because standard Revit filtering techniques fail when searching for nested families in a
/// family document, we have no choice but to iterate over all elements in the family.
/// While there usually aren't that many elements at the family level, nonetheless this method
/// has been built for MAXIMUM SPEED.
/// </remarks>
/// <returns>
/// A collection of matching nested family file instances.
/// </returns>
public static List<FamilyInstance>
GetFilteredNestedFamilyInstances(
string familyFileNameFilter,
string typeNameFilter,
Document familyDocument,
bool caseSensitiveFiltering )
{
// Following good SOA practices, verify the
// incoming data can be worked with.
ValidateFamilyDocument( familyDocument ); // Throws an exception if not a family doc
// The filters can be null
List<FamilyInstance> oResult
= new List<FamilyInstance>();
FamilyInstance oFamilyInstanceCandidate;
FamilySymbol oFamilySymbolCandidate;
List<Family> oMatchingNestedFamilies
= new List<Family>();
List<FamilyInstance> oAllFamilyInstances
= new List<FamilyInstance>();
bool bFamilyFileNameFilterExists = true;
bool bTypeNameFilterExists = true;
// Set up some fast-to-test boolean values, which will be
// used for short-circuit Boolean evaluation later.
if( string.IsNullOrEmpty( familyFileNameFilter ) )
{
bFamilyFileNameFilterExists = false;
}
if( string.IsNullOrEmpty( typeNameFilter ) )
{
bTypeNameFilterExists = false;
}
// Unfortunately detecting nested families in a family document requires iterating
// over all the elements in the document, because the built-in filtering mechanism
// doesn't work for this case. However, families typically don't have nearly as many
// elements as a whole project, so the performance hit shouldn't be too bad.
// Still, the fastest performance should come by iterating over all elements in the given
// family document exactly once, keeping subsets of the family instances found for
// later testing against the nested family file matches found.
ElementClassFilter fFamilyClass = new ElementClassFilter( typeof( Family ) );
ElementClassFilter fFamInstClass = new ElementClassFilter( typeof( FamilyInstance ) );
LogicalOrFilter f = new LogicalOrFilter( fFamilyClass, fFamInstClass );
FilteredElementCollector collector = new FilteredElementCollector( familyDocument );
collector.WherePasses( f );
foreach( Element e in collector )
{
// See if this is a family file nested into the current family document.
Family oNestedFamilyFileCandidate = e as Family;
if( oNestedFamilyFileCandidate != null )
{
// Must ask the "Element" version for it's name, because the Family object's
// name is always the empty string.
if( !bFamilyFileNameFilterExists
|| FilterMatches( oNestedFamilyFileCandidate.Name,
familyFileNameFilter, caseSensitiveFiltering ) )
{
// This is a nested family file, and either no valid family file name filter was
// given, or the name of this family file matches the filter.
oMatchingNestedFamilies.Add( oNestedFamilyFileCandidate );
}
}
else
{
// This element is not a nested family file definition, see if it's a
// nested family instance.
oFamilyInstanceCandidate
= e as FamilyInstance;
if( oFamilyInstanceCandidate != null )
{
// Just add the family instance to our "all" collection for later testing
// because we may not have yet found all the matching nested family file
// definitions.
oAllFamilyInstances.Add( oFamilyInstanceCandidate );
}
}
} // End iterating over all the elements in the family document exactly once
// See if any matching nested family file definitions were found. Only do any
// more work if at least one was found.
foreach( Family oMatchingNestedFamilyFile
in oMatchingNestedFamilies )
{
// Count backwards through the all family instances list. As we find
// matches on this iteration through the matching nested families, we can
// delete them from the candidates list to reduce the number of family
// instance candidates to test for later matching nested family files to be tested
for( int iCounter = oAllFamilyInstances.Count - 1;
iCounter >= 0; iCounter-- )
{
oFamilyInstanceCandidate
= oAllFamilyInstances[iCounter];
#if _2010
oFamilySymbolCandidate
= oFamilyInstanceCandidate.ObjectType
as FamilySymbol;
#endif // _2010
ElementId id = oFamilyInstanceCandidate.GetTypeId();
oFamilySymbolCandidate = familyDocument.GetElement( id )
as FamilySymbol;
if( oFamilySymbolCandidate.Family.UniqueId
== oMatchingNestedFamilyFile.UniqueId )
{
// Only add this family instance to the results if there was no type name
// filter, or this family instance's type matches the given filter.
if( !bTypeNameFilterExists
|| FilterMatches( oFamilyInstanceCandidate.Name,
typeNameFilter, caseSensitiveFiltering ) )
{
oResult.Add( oFamilyInstanceCandidate );
}
// No point in testing this one again,
// since we know its family definition
// has already been processed.
oAllFamilyInstances.RemoveAt( iCounter );
}
} // Next family instance candidate
} // End of for each matching nested family file definition found
return oResult;
}
/// <summary>
/// Returns a reference to the FAMILY parameter (as a simple Parameter data type) on the given instance
/// for the parameter with the given name. Will return the parameter
/// whether it is an instance or type parameter.
/// Returns null if no parameter on the instance was found.
/// </summary>
/// <param name="nestedFamilyInstance">An instance of a nested family file</param>
/// <param name="parameterName">The name of the desired parameter to get a reference to</param>
/// <remarks>
/// Even though the data type returned is the more generic Parameter type, it will
/// actually be for the data of the internal FamilyParameter object.
/// </remarks>
/// <returns></returns>
public static Parameter GetFamilyParameter(
FamilyInstance nestedFamilyInstance,
string parameterName )
{
// Following good SOA practices, verify the
// incoming parameters before attempting to proceed.
if( nestedFamilyInstance == null )
{
throw new ArgumentNullException(
"nestedFamilyInstance" );
}
if( string.IsNullOrEmpty( parameterName ) )
{
throw new ArgumentNullException(
"parameterName" );
}
Parameter oResult = null;
//See if the parameter is an Instance parameter
oResult = nestedFamilyInstance.get_Parameter(
parameterName );
// No? See if it's a Type parameter
if( oResult == null )
{
oResult = nestedFamilyInstance.Symbol.get_Parameter(
parameterName );
}
return oResult;
}
/// <summary>
/// This method takes an instance of a nested family and links a parameter on it to
/// a parameter on the given host family instance. This allows a change at the host
/// level to automatically be sent down and applied to the nested family instance.
/// </summary>
/// <param name="hostFamilyDocument">The host family document to have one of its parameters be linked to a parameter on the given nested family instance</param>
/// <param name="nestedFamilyInstance">The nested family whose parameter should be linked to a parameter on the host family</param>
/// <param name="nestedFamilyParameterName">The name of the parameter on the nested family to link to the host family parameter</param>
/// <param name="hostFamilyParameterNameToLink">The name of the parameter on the host family to link to a parameter on the given nested family instance</param>
public static void
LinkNestedFamilyParameterToHostFamilyParameter(
Document hostFamilyDocument,
FamilyInstance nestedFamilyInstance,
string nestedFamilyParameterName,
string hostFamilyParameterNameToLink )
{
// Following good SOA practices, verify the incoming
// parameters before attempting to proceed.
ValidateFamilyDocument( hostFamilyDocument ); // Throws an exception if is not valid family doc
if( nestedFamilyInstance == null )
{
throw new ArgumentNullException(
"nestedFamilyInstance" );
}
if( string.IsNullOrEmpty( nestedFamilyParameterName ) )
{
throw new ArgumentNullException(
"nestedFamilyParameterName" );
}
if( string.IsNullOrEmpty( hostFamilyParameterNameToLink ) )
{
throw new ArgumentNullException(
"hostFamilyParameterNameToLink" );
}
Parameter oNestedFamilyParameter
= GetFamilyParameter( nestedFamilyInstance,
nestedFamilyParameterName );
if( oNestedFamilyParameter == null )
{
throw new Exception( "Parameter '"
+ nestedFamilyParameterName
+ "' was not found on the nested family '"
+ nestedFamilyInstance.Symbol.Name + "'" );
}
FamilyParameter oHostFamilyParameter
= hostFamilyDocument.FamilyManager.get_Parameter(
hostFamilyParameterNameToLink );
if( oHostFamilyParameter == null )
{
throw new Exception( "Parameter '"
+ hostFamilyParameterNameToLink
+ "' was not found on the host family." );
}
hostFamilyDocument.FamilyManager
.AssociateElementParameterToFamilyParameter(
oNestedFamilyParameter, oHostFamilyParameter );
}
#endregion Public Methods
#region Private Helper Methods
/// <summary>
/// Returns whether or not the nameToCheck matches the given filter.
/// This is done with a simple Contains check, so wildcards won't work.
/// </summary>
/// <param name="nameToCheck">The name (e.g. type name or family file name) to check for a match with the filter</param>
/// <param name="filter">The filter to compare to</param>
/// <param name="caseSensitiveComparison">Whether or not the comparison is case-sensitive.</param>
/// <returns></returns>
private static bool FilterMatches(
string nameToCheck,
string filter,
bool caseSensitiveComparison )
{
bool bResult = false;
if( string.IsNullOrEmpty( nameToCheck ) )
{
// No name given, so the call must fail.
return false;
}
if( string.IsNullOrEmpty( filter ) )
{
// No filter given, so the given name passes the test
return true;
}
if( !caseSensitiveComparison )
{
// Since the String.Contains function only does case-sensitive checks,
// cheat with our copies of the values which we'll use for the comparison.
nameToCheck = nameToCheck.ToUpper();
filter = filter.ToUpper();
}
bResult = nameToCheck.Contains( filter );
return bResult;
}
/// <summary>
/// This method will validate the provided Revit Document to make sure the reference
/// exists and is for a FAMILY document. It will throw an ArgumentNullException
/// if nothing is sent, and will throw an ArgumentOutOfRangeException if the document
/// provided isn't a family document (e.g. is a project document)
/// </summary>
/// <param name="document">The Revit document being tested</param>
private static void ValidateFamilyDocument(
Document document )
{
if( null == document )
{
throw new ArgumentNullException( "document" );
}
if( !document.IsFamilyDocument )
{
throw new ArgumentOutOfRangeException(
"The document provided is not a Family Document." );
}
}
#endregion Private Helper Methods
}
[Transaction( TransactionMode.ReadOnly )]
class CmdNestedFamilies : IExternalCommand
{
public Result Execute(
ExternalCommandData commandData,
ref string message,
ElementSet elements )
{
UIApplication app = commandData.Application;
Document doc = app.ActiveUIDocument.Document;
string familyFilenameFilter = string.Empty;
string typeNameFilter = string.Empty;
bool caseSensitive = false;
IEnumerable<Family> nestedFamilies
= NestedFamilyFunctions.GetFilteredNestedFamilyDefinitions(
familyFilenameFilter, doc, caseSensitive );
foreach( Family f in nestedFamilies )
{
Debug.WriteLine( f.Name );
}
List<FamilyInstance> instances
= NestedFamilyFunctions.GetFilteredNestedFamilyInstances(
familyFilenameFilter, typeNameFilter, doc, caseSensitive );
foreach( FamilyInstance fi in instances )
{
Debug.WriteLine( Util.ElementDescription( fi ) );
}
return Result.Failed;
}
}
}