The day before yesterday, I presented the contents of Steven Campbell's key family concepts and parametric family classes from the Revit API DevCamp in Moscow, and an overview of the related family API topics representing my humble contribution to the latter.
If you read that post immediately after it was first published, I suggest you go back and have a quick look again now, since I revisited it and added a significant chunk of information after the initial publication.
Now I would like to delve deeper into the detailed API aspects.
Before proceeding with that, let me mention two other good bits of news:
I just finished reading Trillions – Thriving in the Emerging Information Technology by Peter Lucas, Joe Ballay, Mickey McManus of MAYA Design.
This is the first pure technology related book I have read all the way through in ages, and I am very enthused. It was distributed to all participants of the internal Autodesk Tech Summit in Boston and discusses design and technology for pervasive computing.
It provides numerous shockingly obvious and often quite old insights, a very critical view of our current use of Internet and so-called cloud technologies, and an inspiring and thrilling read.
The topic is how to harness and live in – as opposed to 'with' – the unbounded complexity of the trillion-node network of connected devices that we are inevitable heading towards, and will lead to catastrophe unless we radically change some fundamental aspects of the ways we handle and share information.
I highly recommend reading this book.
I highly recommend abstaining from all use of the word 'cloud computing' until you have completed it :-)
Visual Studio 2013 and the CLR 4.5.1 now support 64-bit Edit and Continue when debugging C# and VB applications. Stephen Preston is happy about that.
You can download the preview version of Visual Studio 2013 to test it right away.
64-bit Edit and Continue for C++ is not yet supported. Please vote for 64-bit Edit and Continue for C++ to be implemented as well.
You have three votes, and you can apply them all to this one wish, if you like.
Getting back to the Revit API and programmatic work with families in particular:
As I pointed out, Steve's presentation covers key family editor concepts from a user point of view. From a programming point of view, there are two main family related areas to consider:
Since a family instance can also be inserted into a family document to create a nested family structure, the second point is actually relevant both in the family and project context. It includes tasks such as loading a family, placing instances, manipulating types and existing instances.
Both of these aspects represent large important topics that have been frequently discussed here in the past.
Before Revit 2010, only the second area was covered by the API. Programmatic creation of families and the entire use of the API in the family context was impossible before the introduction of the Family API.
The basic steps to programmatically create a family from scratch are demonstrated in detail in section 3, Revit Family API, of the ADN Revit API training material.
The programmatic creation and insertion of an extrusion family provides a more recent and simplified example of defining and loading a family and placing an instance without exploring the more complex and interesting aspects of the definition such as setting alignments and adding types.
The family API topics concluding the preceding post list the programming concepts that Steve and I decided to revisit:
It also provides a full snapshot of the source code and Visual Studio solution implementing the three external commands implementing these concepts and an external application wrapper defining a nice compact user interface to access and launch them:
Here is the list of topics to explain their implementation and functionality in full detail:
I'll discuss scenario 1 right here and now, and return to the rest in the next few days.
The external command CmdTableLoadPlace demonstrates loading a family and placing family instances.
The loading requires a prior check whether the family is already present in the project.
Placing the instances can be done either completely automatically or by prompting the user for their locations.
The former makes use of one of the many overloads of the NewFamilyInstance method (how to test all NewFamilyInstance overloads) and is not discussed here.
The latter can be implemented using PromptForFamilyInstancePlacement, in which case the retrieval of the newly placed instances requires an interesting additional little twist.
Before we can start placing any family instances, we need to load our family definition.
Loading it into the project will cause an error if it is already present beforehand, so we need to check for its prior existence before attempting.
All retrieval of database elements is performed using filtered element collectors. Effective element filtering requires application of as many filters as possible to limit the number of elements retrieved.
Of these, the quick filters are highly preferred, since they enable filtering of elements without loading the entire element information into memory. Slow filters are also effective, since they enable checking of element properties inside the Revit memory space before the element information is marshalled and transferred out into the .NET universe. The slowest filtering is achieved by post-processing the element data in .NET after extracting it from Revit.
Assuming that we do not have a terribly large number of families loaded in the project, we can get by in this case by looking at all the families and applying a .NET post-processing step filtering for the desired family name, e.g. using a language integrated LINQ query and the generic FirstOrDefault method like this:
FilteredElementCollector a = new FilteredElementCollector( doc ) .OfClass( typeof( Family ) ); Family family = a.FirstOrDefault<Element>( e => e.Name.Equals( FamilyName ) ) as Family;
We check whether the family is already loaded by retrieving all element by filtering of a specific class, also known as .NET type, in this case the Family class, and then post-processing the results looking for a given target name.
This functionality is useful for many different kinds of searches, which led to us already discussing a helper function implementing it, as well as possible optimisations to it.
In this add-in, for the same of simplicity, I implement it like this without any further attempts at optimisation:
/// <summary> /// Retrieve a database element /// of the given type and name. /// </summary> public static Element FindElementByName( Document doc, Type targetType, string targetName ) { return new FilteredElementCollector( doc ) .OfClass( targetType ) .FirstOrDefault<Element>( e => e.Name.Equals( targetName ) ); }
This add-in makes use of this helper method for three different purposes:
Once we have determined that the required family is not yet present in the project, loading it is a trivial task:
if( null == family ) { // It is not present, so check for // the file to load it from: if( !File.Exists( FamilyPath ) ) { Util.ErrorMsg( string.Format( "Please ensure that the sample table " + "family file '{0}' exists in '{1}'.", FamilyName, _family_folder ) ); return Result.Failed; } // Load family from file: using( Transaction tx = new Transaction( doc ) ) { tx.Start( "Load Family" ); doc.LoadFamily( FamilyPath, out family ); tx.Commit(); } }
I added a check to ensure that the family definition file is available in the expected location.
Loading the family modifies the database, so a transaction is required.
If you only need a few symbols (also known as types) from a large family, you can load them more effectively one by one by using LoadFamilySymbol instead of loading them all at once through the single call to LoadFamily.
Here is a detailed discussion and implementation of loading only selected family types.
As said, we decided to manually place the table instances in this particular sample application, which is easily implemented using PromptForFamilyInstancePlacement.
The only input required is the symbol to be placed:
// Determine the family symbol FamilySymbol symbol = null; foreach( FamilySymbol s in family.Symbols ) { symbol = s; // Our family only contains one // symbol, so pick it and leave break; } // Place the family symbol: // PromptForFamilyInstancePlacement cannot // be called inside transaction. uidoc.PromptForFamilyInstancePlacement( symbol );
Note that this method call requires that no transactions be open, presumably because Revit prefers taking care of that internally instead.
As already noted, the retrieval of the instances placed by the call to PromptForFamilyInstancePlacement requires an interesting additional little twist.
Theoretically, this method could return a list of the instance element ids. Lacking that, we can find out for ourselves by temporarily registering to the DocumentChanged event before the call, unregistering immediately afterwards, and making a note of all element ids added in between.
Here is the OnDocumentChanged event handler and the element id container I use for that:
/// <summary> /// Collection of newly added family instances /// </summary> List<ElementId> _added_element_ids = new List<ElementId>(); void OnDocumentChanged( object sender, DocumentChangedEventArgs e ) { _added_element_ids.AddRange( e.GetAddedElementIds() ); }
Here is the full source code showing the event registration details to activate this.
For completeness sake, here is the full source code of the CmdTableLoadPlace external command showing the steps described above in their complete glorious, elegant and cooperative context:
/// <summary> /// Load table family if not already present and /// place table family instances. /// </summary> [Transaction( TransactionMode.Manual )] public class CmdTableLoadPlace : IExternalCommand { /// <summary> /// Family name. /// </summary> public const string FamilyName = "family_api_table"; /// <summary> /// Family file path. /// Normally, you would either search the library /// paths provided by Application.GetLibraryPaths /// method. In this case, we store the sample /// family in the same location as the add-in. /// </summary> //const string _family_folder = "Z:/a/rvt"; static string _family_folder = Path.GetDirectoryName( typeof( CmdTableLoadPlace ) .Assembly.Location ); /// <summary> /// Family filename extension RFA. /// </summary> const string _family_ext = "rfa"; /// <summary> /// Family file path /// </summary> static string _family_path = null; /// <summary> /// Return complete family file path /// </summary> static string FamilyPath { get { if( null == _family_path ) { _family_path = Path.Combine( _family_folder, FamilyName ); _family_path = Path.ChangeExtension( _family_path, _family_ext ); } return _family_path; } } /// <summary> /// Collection of newly added family instances /// </summary> List<ElementId> _added_element_ids = new List<ElementId>(); /// <summary> /// External command mainline /// </summary> public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; // Retrieve the family if it is already present: Family family = Util.FindElementByName( doc, typeof( Family ), FamilyName ) as Family; if( null == family ) { // It is not present, so check for // the file to load it from: if( !File.Exists( FamilyPath ) ) { Util.ErrorMsg( string.Format( "Please ensure that the sample table " + "family file '{0}' exists in '{1}'.", FamilyName, _family_folder ) ); return Result.Failed; } // Load family from file: using( Transaction tx = new Transaction( doc ) ) { tx.Start( "Load Family" ); doc.LoadFamily( FamilyPath, out family ); tx.Commit(); } } // Determine the family symbol FamilySymbol symbol = null; foreach( FamilySymbol s in family.Symbols ) { symbol = s; // Our family only contains one // symbol, so pick it and leave break; } // Place the family symbol: // Subscribe to document changed event to // retrieve family instance elements added by the // PromptForFamilyInstancePlacement operation: app.DocumentChanged += new EventHandler<DocumentChangedEventArgs>( OnDocumentChanged ); _added_element_ids.Clear(); // PromptForFamilyInstancePlacement cannot // be called inside transaction. uidoc.PromptForFamilyInstancePlacement( symbol ); app.DocumentChanged -= new EventHandler<DocumentChangedEventArgs>( OnDocumentChanged ); // Access the newly placed family instances: int n = _added_element_ids.Count(); string msg = string.Format( "Placed {0} {1} family instance{2}{3}", n, family.Name, Util.PluralSuffix( n ), Util.DotOrColon( n ) ); string ids = string.Join( ", ", _added_element_ids.Select<ElementId, string>( id => id.IntegerValue.ToString() ) ); Util.InfoMsg2( msg, ids ); return Result.Succeeded; } void OnDocumentChanged( object sender, DocumentChangedEventArgs e ) { _added_element_ids.AddRange( e.GetAddedElementIds() ); } }
As said, the detailed description of the other two commands will follow soon, and the complete Visual Studio solution is available from the overview of the family API topics concluding the preceding post.
I wish you a wonderful weekend.
I just finished reading Trillions – Thriving in the Emerging Information Technology by Peter Lucas, Joe Ballay, Mickey McManus of MAYA Design, distributed to all Tech Summit presenters.
It discusses design and technology for pervasive computing, provides numerous shockingly obvious and often quite old insights, a very critical view of our current use of Internet and so-called cloud technologies, and an inspiring and thrilling read.
The topic is how to harness and live in – as opposed to 'with' – the unbounded complexity of the trillion-node network of connected devices that we are inevitable heading towards, and will lead to catastrophe unless we radically change some fundamental aspects of the ways we handle and share information.
I highly recommend reading this book.
I highly recommend abstaining from all use of the word 'cloud computing' until you have completed it :-)