Moving away from the topic of geometry, I have another sample that I would like to share from the Revit API introductory workshop that I recently held. In this sample, we address the following topics:
- Using a Boolean combination of Revit API filters to find certain elements.
- Retrieving the hosted to host relationship between doors, windows, and walls.
- Inverting the relationship, i.e. determining the host to hosted one.
This sample also introduces some new little utility functions, such as ElementDescription() to return a string describing a given Revit element.
As said, we are interested in the relationship between door and window openings and the walls hosting them. The Revit API provides a one-way relationship from hosted element to its host in the form of the FamilyInstance.Host property, which returns the containing element if the family instance is contained within another element. In our case, the doors and windows are family instances, and the containing element is a wall.
In a first step, we create a Boolean combination of filters to select the elements we are interested in from the building model database. We do not need to retrieve the walls, because they do not provide any relationship information about their hosted elements. All we need is to access the doors and windows. These can be identified by having the class FamilyInstance and either door or window category. We can use a TypeFilter to match the class, and CategoryFilter instances for the categories. The category filters can be combined using a Boolean OR to match either door or window. The resulting filter can in turn be combined with the class filter using a Boolean AND. The result is described in the code below as
f5 = f1 && f4 = f1 && (f2 || f3) = family instance and (door or window)
Once all doors and windows in the model have been retrieved, we can simply loop through them and query the host of each. For each host element id, we create a new entry into a dictionary using the host element id as a key and a list of all hosted element ids as its value. The elements being processed are also logged to the Visual Studio debug output window using the Debug.WriteLine() method provided by the very useful System.Diagnostics namespace.
At this point, we are actually already done. The resulting dictionary now implements the inverted relationship. We started out with each hosted element knowing its host, and now we have the dictionary with the host element id as key, and a list of ids of all its hosted elements as a value. All that remains to do is list this information in the debug output window, or use it in some other way.
Here is the code for this little command:
#region Namespaces using System; using System.Collections.Generic; using System.Diagnostics; using Autodesk.Revit; using Autodesk.Revit.Elements; using Autodesk.Revit.Parameters; using CmdResult = Autodesk.Revit.IExternalCommand.Result; #endregion // Namespaces namespace BuildingCoder { public class CmdRelationshipInverter : IExternalCommand { private Document m_doc; public static string PluralSuffix( int n ) { return 1 == n ? "" : "s"; } public static string DotOrColon( int n ) { return 1 < n ? ":" : "."; } string ElementDescription( Element e ) { // for a wall, the element name equals the // wall type name, which is equivalent to the // family name ... FamilyInstance fi = e as FamilyInstance; string fn = string.Empty; if( null != fi ) { fn = fi.Symbol.Family.Name + " "; } return string.Format( "{0} {1}<{2} {3}>", e.Category.Name, fn, e.Id.Value.ToString(), e.Name ); } string ElementDescription( ElementId id ) { Element e = m_doc.get_Element( ref id ); return ElementDescription( e ); } private Dictionary<ElementId, List<ElementId>> getElementIds( List<Element> elements ) { Dictionary<ElementId, List<ElementId>> dict = new Dictionary<ElementId, List<ElementId>>(); string fmt = "{0} is hosted by {1}"; foreach( FamilyInstance fi in elements ) { ElementId id = fi.Id; ElementId idHost = fi.Host.Id; Debug.WriteLine( string.Format( fmt, ElementDescription( fi ), ElementDescription( idHost ) ) ); if( !dict.ContainsKey( idHost ) ) { dict.Add( idHost, new List<ElementId>() ); } dict[idHost].Add( id ); } return dict; } private void dumpHostedElements( Dictionary<ElementId, List<ElementId>> ids ) { foreach( ElementId idHost in ids.Keys ) { string s = string.Empty; foreach( ElementId id in ids[idHost] ) { if( 0 < s.Length ) { s += ", "; } s += ElementDescription( id ); } int n = ids[idHost].Count; Debug.WriteLine(string.Format( "{0} hosts {1} opening{2}: {3}", ElementDescription( idHost ), n, PluralSuffix( n ), s ) ); } } public CmdResult Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { Application app = commandData.Application; m_doc = app.ActiveDocument; // f5 = f1 && f4 // = f1 && (f2 || f3) // = family instance and (door or window) Autodesk.Revit.Creation.Filter cf = app.Create.Filter; Filter f1 = cf.NewTypeFilter( typeof( FamilyInstance ) ); Filter f2 = cf.NewCategoryFilter( BuiltInCategory.OST_Doors ); Filter f3 = cf.NewCategoryFilter( BuiltInCategory.OST_Windows ); Filter f4 = cf.NewLogicOrFilter( f2, f3 ); Filter f5 = cf.NewLogicAndFilter( f1, f4 ); List<Element> openings = new List<Element>(); int n = m_doc.get_Elements( f5, openings ); // map with key = host element id and // value = list of hosted element ids: Dictionary<ElementId, List<ElementId>> ids = getElementIds( openings ); dumpHostedElements( ids ); m_doc = null; return CmdResult.Succeeded; } } }
Here is a log file from a sample run. As you can see, we first traverse all the hosted elements and determine the hosting wall of each. These are inserted into the dictionary representing the inverse relationship, which dumps its contents using dumpHostedElements() before the command ends. Please excuse the long overflowing lines. To see them in full, you can copy and paste them into an editor such as notepad:
Doors M_Single-Flush <127252 0915 x 2134mm> is hosted by Walls <127248 Generic - 200mm> Windows M_Fixed <127255 0406 x 0610mm> is hosted by Walls <127248 Generic - 200mm> Windows M_Fixed <127258 0406 x 0610mm> is hosted by Walls <127248 Generic - 200mm> Doors M_Single-Flush <127295 0915 x 2134mm> is hosted by Walls <127167 Generic - 200mm> Doors M_Single-Flush <127331 0915 x 2134mm> is hosted by Walls <127167 Generic - 200mm> Windows M_Fixed <127356 0406 x 0610mm> is hosted by Walls <127240 Generic - 200mm> Windows M_Fixed <127411 0406 x 0610mm> is hosted by Walls <127240 Generic - 200mm> Windows M_Fixed <127436 0406 x 0610mm> is hosted by Walls <127240 Generic - 200mm> Windows M_Fixed <127462 0406 x 0610mm> is hosted by Walls <127215 Generic - 200mm> Windows M_Fixed <127484 0406 x 0610mm> is hosted by Walls <127215 Generic - 200mm> Windows M_Fixed <127502 0406 x 0610mm> is hosted by Walls <127215 Generic - 200mm> Windows M_Fixed <127526 0406 x 0610mm> is hosted by Walls <127215 Generic - 200mm> Walls <127248 Generic - 200mm> hosts 3 openings: Doors M_Single-Flush <127252 0915 x 2134mm>, Windows M_Fixed <127255 0406 x 0610mm>, Windows M_Fixed <127258 0406 x 0610mm> Walls <127167 Generic - 200mm> hosts 2 openings: Doors M_Single-Flush <127295 0915 x 2134mm>, Doors M_Single-Flush <127331 0915 x 2134mm> Walls <127240 Generic - 200mm> hosts 3 openings: Windows M_Fixed <127356 0406 x 0610mm>, Windows M_Fixed <127411 0406 x 0610mm>, Windows M_Fixed <127436 0406 x 0610mm> Walls <127215 Generic - 200mm> hosts 4 openings: Windows M_Fixed <127462 0406 x 0610mm>, Windows M_Fixed <127484 0406 x 0610mm>, Windows M_Fixed <127502 0406 x 0610mm>, Windows M_Fixed <127526 0406 x 0610mm>
For your convenience, I am adding the complete Visual Studio solution here. This version 1.0.0.3 includes the three commands we discussed so far: CmdListWalls, CmdRelationshipInverter and CmdWallDimensions.
Hello Jeremy, what is the error?
I create a level and then new wall and a door and It draw the 2 objects in diferent levels.
Level nivel0 = doccreation.NewLevel(50);
doccreation.NewViewPlan(nivel0.Name, nivel0, ViewType.FloorPlan);
Wall muro1 = doccreation.NewWall(linea1, nivel0, true);
FamilyInstance fi = doc.Create.NewFamilyInstance(midpoint, door, muro1,nivel0, StructuralType.NonStructural);
Thanks
Posted by: Berria | January 21, 2009 at 12:01
Dear Berria,
I cannot tell you off-hand what the problem is in your situation, but I can provide you with a code example that achieves your goal. Look at the external command Lab2_0_CreateLittleHouse in the Revit API introduction labs, which are available at
http://thebuildingcoder.typepad.com/blog/files/rac_labs_20081117.zip
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 22, 2009 at 04:44
Hello Jeremy,
I need create a house with 2 floor, when I built the first is all OK, and the error is when I build the second, because de level to start the componet posicion is always 0.
Best regards
Posted by: Berria | January 22, 2009 at 04:57
Hi Berria,
I cannot really dive deeply into this, but I have rewritten the code I pointed you to above to create two sets of walls on two levels, with a door in each set. I have posted the new code in a separate post:
http://thebuildingcoder.typepad.com/blog/2009/01/walls-and-doors-on-two-levels.html
You need to ensure that the Revit model has three levels, and that they come in the expected order, for my simple test command to work. Please appreaciate that I do not have any more time to spend on this now.
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 22, 2009 at 10:09
Hi Jeremy,
Thank you very much, I create my walls and component with your code.
Now I have problem with a level of the floor, I apply the similar code in roof and its Ok, can you say me where is the error??
Level levelBottom = doccreation.NewLevel(0);
Level nivel1 = doccreation.NewLevel(15);
Floor floor1 = doc.Create.NewFloor(profile, floorType, nivel1, false);
FootPrintRoof roof = doc.Create.NewFootPrintRoof(profile, nivel1, roofType, footPrintToModelCurvesMapping);
The result is, floor posicion in the bottomlevel and roof in nivel1, I dont understand nothing.
Very, very thanks
Posted by: Berria | January 28, 2009 at 06:41
Hi Jeremy,
Thank you very much, I create my walls and component with your code.
Now I have problem with a level of the floor, I apply the similar code in roof and its Ok, can you say me where is the error??
Level levelBottom = doccreation.NewLevel(0);
Level nivel1 = doccreation.NewLevel(15);
Floor floor1 = doc.Create.NewFloor(profile, floorType, nivel1, false);
FootPrintRoof roof = doc.Create.NewFootPrintRoof(profile, nivel1, roofType, footPrintToModelCurvesMapping);
The result is, floor posicion in the bottomlevel and roof in nivel1, I dont understand nothing.
Very, very thanks
Posted by: Berria | January 28, 2009 at 06:44
Hi Berria,
I don't understand nothing either :-)
Looks to me as if you are creating the floor and the roof in the same level 'nivel1'?
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 28, 2009 at 07:57
Sorry, the code is this:
Level levelBottom = doccreation.NewLevel(0);
Level nivel1 = doccreation.NewLevel(15);
Level nivel2 = doccreation.NewLevel(30);
Floor floor1 = doc.Create.NewFloor(profile, floorType, nivel1, false);
FootPrintRoof roof = doc.Create.NewFootPrintRoof(profile, nivel2, roofType, footPrintToModelCurvesMapping);
The result is, floor posicion in the bottomlevel and roof in nivel2.
Posted by: Berria | January 28, 2009 at 08:41
Hi Berria,
Maybe you can try changing the Z coordinate of the profile before adding the second floor?
Cheers, Jeremy.
Posted by: Jeremy Tammik | January 29, 2009 at 02:55
Hey Jeremy.
How can I get the id of the host using VB code please? I'm currently getting the host name like this:
param = elem.get_Parameter("Host")
writetagline("host", param.AsString)
(writetagline is a function I made that exports the name to an external .xml file.)
But what I really need is the id of the host.
Thanks!
Posted by: Nicholas | March 11, 2009 at 15:23
Hi Nicholas,
As you can see it in the above code sample if the element is actually a family instance, then you can get to its host's id like this: "ElementId idHost = fi.Host.Id;"
Am I missing something? :-S
Cheers,
Adam
Posted by: Adam Nagy | March 17, 2009 at 08:40
Hey Adam,
I'm such a noob!
I managed to get the Host name and id like this:
Dim fi As FamilyInstance = e
MsgBox(fi.Host.Name.ToString)
MsgBox(fi.Host.Id.Value.ToString)
Thanks for pointing me in the right direction!
Nicholas
Posted by: Nicholas | March 26, 2009 at 14:19
Hi Jeremy,
I've a question about "Doors Direction" and I don't know where should I post this question. So sorry if it's the wrong place for it :-)
Now my question: How can I find out in which direction doors will be opened? A unit vector like (0,1,0) will be also a good solution for me.
DANKE!
puyan
Posted by: puyan | May 22, 2009 at 10:42
Dear Pyan,
Please have a look at the DoorSwing Revit SDK sample and the "Revit 2010 API Developer Guide.pdf" document, especially section 10.3.1.1 Orientation, Figure 56: Doors with different Facing and Hand Orientations, and Table 26: Different Instances of the Same Type.
Bitte sehr and cheers, Jeremy.
Posted by: Jeremy Tammik | May 22, 2009 at 11:43