We already had a look at the issue of linked files and how to hide them. A frequent question in this context is how to access the elements in linked files. This can be very simple, actually, as we will demonstrate. I had a discussion on this with Joel Karr of Environmental Systems Design, Inc. He is implementing a command to order to monitor and compare the lighting fixtures in an MEP model with the ones defined in a linked in architectural model. He very kindly provided a sample application listing the lighting fixture elements contained in a linked file, as a starting point for implementing a comparison of those with the ones in the current model. I rewrote and significantly simplified his application and find that we can achieve a lot of functionality with minimal effort.
For this purpose we implemented a new external command CmdLinkedFileElements which iterates over all open documents, which includes the linked ones as well, and displays selected properties for all lighting fixture elements in a data grid.
We define a class ElementData to manage the properties we wish to display:
These data items are stored in individual private members. We define a constructor in order to initialise all the members for a given Revit element. By defining public properties for accessing each data item to display, we can save the effort of transferring data manually into the form. Here is the class implementation including member data, constructor, and accessors:
public class ElementData { string _document; string _elementName; int _id; double _x; double _y; double _z; string _uniqueId; string _folder; public ElementData( string path, string elementName, int id, double x, double y, double z, string uniqueId ) { int i = path.LastIndexOf( "\\" ); _document = path.Substring( i + 1 ); _elementName = elementName; _id = id; _x = x; _y = y; _z = z; _uniqueId = uniqueId; _folder = path.Substring( 0, i ); } public string Document { get { return _document; } } public string Element { get { return _elementName; } } public int Id { get { return _id; } } public string X { get { return Util.RealString( _x ); } } public string Y { get { return Util.RealString( _y ); } } public string Z { get { return Util.RealString( _z ); } } public string UniqueId { get { return _uniqueId; } } public string Folder { get { return _folder; } } }
With these properties defined, displaying the data in the form is handled completely automatically by one single line in the form constructor:
public CmdLinkedFileElementsForm( List<ElementData> a ) { InitializeComponent(); dataGridView1.DataSource = a; }
We have discussed how to display the element data in a data grid. Before we can display it, we need to retrieve it from the Revit database. To do so, we can simply iterate over all the open documents in the application. If desired, we can implement a filter to eliminate the documents that do not represent a linked file.
We implement a utility method to filter for elements matching a given category and type.
public List<Element> GetElements( BuiltInCategory bic, Type elemType, Application app, Document doc ) { CreationFilter cf = app.Create.Filter; Filter f1 = cf.NewCategoryFilter( bic ); Filter f2 = cf.NewTypeFilter( elemType ); Filter f3 = cf.NewLogicAndFilter( f1, f2 ); List<Element> elements = new List<Element>(); doc.get_Elements( f3, elements ); return elements; }
This can be used both to extract the lighting fixtures to display in the form as well as the linked files instances, if we need those. We will only use it for the lighting fixtures in the real code. For completeness sake, this would be the call to retrieve the link instances:
List<Element> links = GetElements( BuiltInCategory.OST_RvtLinks, typeof( Instance ), app, doc );
The external command performs the following steps:
Here is the mainline for the command:
List<ElementData> data = new List<ElementData>(); Application app = commandData.Application; DocumentSet docs = app.Documents; foreach( Document doc in docs ) { List<Element> elements = GetElements( BuiltInCategory.OST_LightingFixtures, typeof( FamilyInstance ), app, doc ); foreach( FamilyInstance e in elements ) { string name = e.Name; LocationPoint lp = e.Location as LocationPoint; if( null != lp ) { XYZ p = lp.Point; data.Add( new ElementData( doc.PathName, e.Name, e.Id.Value, p.X, p.Y, p.Z, e.UniqueId ) ); } } } using( CmdLinkedFileElementsForm dlg = new CmdLinkedFileElementsForm( data ) ) { dlg.ShowDialog(); } return CmdResult.Cancelled;
Notice how short and sweet this is?
Here is an example of the command displaying the data in the sample model provided by Joel:
This can obviously easily be adapted to handle other element types or more general selections than just lighting fixtures. For the moment, of course, we are ignoring all the thorny issues to do with transformations and stuff. I am looking forward to your comments on this one.
Here is version 1.0.0.27 of the complete Visual Studio solution with the new CmdLinkedFileElements command. It also includes the new command CmdNewRailing which unfortunately does not create a new railing instance. The reasons for this can be found in the discussion with Berria.