In the previous post, we read out materials from walls and other building elements by querying them for their geometry, picking out all the solids, and iterating over their faces. However, for family instances such as doors and windows, we noticed that no solid is present in the top level objects contained by the geometry element.
Family instances make use of a geometry instance to define their geometry. The geometry instance provides the facility to reuse symbol geometry in multiple locations for different family instances of the same symbol. Its most important properties to support this are:
- Symbol: The symbol element that this object is referring to.
- SymbolGeometry: The geometric representation of the symbol.
- Transform: The affine transformation from the local coordinate space of the symbol into the coordinate space of the instance.
In our current task of listing the materials used by the building elements, we do not need to examine the transformations applied to the instance geometry. If we were interested in face coordinates, we would have to keep track of that information.
In our case, to determine materials used in the door and window family instances, all we have to do is add one level of recursion to the existing GetMaterials() implementation. We need to check for geometry instance objects as well as solids. In case of an instance, we can access the symbol geometry and make a recursive call to GetMaterials() to pick out the materials from that as follows:
public List<string> GetMaterials( GeoElement geo ) { List<string> materials = new List<string>(); foreach( GeometryObject o in geo.Objects ) { if( o is Solid ) { Solid solid = o as Solid; foreach( Face face in solid.Faces ) { string s = face.MaterialElement.Name; materials.Add( s ); } } else if( o is GeoInstance ) { GeoInstance i = o as GeoInstance; materials.AddRange( GetMaterials( i.SymbolGeometry ) ); } } return materials; }
Note that we added an additional alias to our namespace references to disambiguate the Revit element and geometry instance classes:
using GeoInstance = Autodesk.Revit.Geometry.Instance;
With the additional code in place, we obtain the same materials as before for the walls, floor and roof, and additionally a long list of new materials for the numerous family instance faces:
Walls <127248 Generic - 200mm> has 17 materials: 0 Default Wall . . . 16 Default Wall Walls <127249 Generic - 200mm> has 6 materials: 0 Default Wall . . . 5 Default Wall Walls <127250 Generic - 200mm> has 6 materials: 0 Default Wall . . . 5 Default Wall Walls <127251 Generic - 200mm> has 6 materials: 0 Default Wall . . . 5 Default Wall Doors M_Single-Flush <127252 0915 x 2134mm> has 26 materials: 0 Door - Frame 1 Door - Frame 2 Door - Frame 3 Door - Frame . . . 19 Door - Frame 20 Door - Panel 21 Door - Panel . . . 25 Door - Panel Windows M_Fixed <127255 0406 x 0610mm> has 36 materials: 0 Glass 1 Glass . . . 5 Glass 6 Sash 7 Sash . . . 35 Sash Windows M_Fixed <127258 0406 x 0610mm> has 36 materials: 0 Glass 1 Glass . . . 5 Glass 6 Sash 7 Sash . . . 35 Sash Floors <127266 Generic Floor - 400mm> has 6 materials: 0 Default Roof . . . 5 Default Roof Roofs <127269 Generic Roof - 300mm> has 12 materials: 0 Default Roof . . . 11 Default Roof
As you can see, the material assignments on the family instance solid faces may allow us to differentiate between the different element components, such as the door frame and panel, and the window glass and sash. This is however dependant on the family definition, so there is no guarantee that this approach will work for all families.
An interesting next step would be to analyse how to make use of the transformations defined by the nested geometry instances to calculate the real world coordinates. How to do this is actually demonstrated by the various geometry viewers that we already discussed.
I am adding the complete Visual Studio solution here. This version 1.0.0.6 includes all commands we discussed so far: CmdListWalls, CmdRelationshipInverter, CmdWallDimensions, CmdFilterPerformance and the updated version of CmdGetMaterials.
Hi Jeremy,
I'm just trying to get the geometry of the elements and I faced a strange behavior which I cannot handle. A have a few instance (railings, furniture, plants) in a model file which has no usable geometry. As a curiosity I tried your CmdGetMaterials on it and it crashed and neither ObjectViwer is able to display anything. The problem is that geo.Objects contains two entities: a Solid which has no faces at all and a GeoInstance which has SymbolGeometry = null. Can you explain to me how should I query for the geometry in this case?
Cheers,
Andras
Posted by: Andras Kiss | June 22, 2009 at 06:01
Dear Andras,
Oh dear, that sounds a bit problematic. I have seen such solids with no faces or edges in many previous cases as well. These seem to be quite common and simply need to be skipped. If the GeoInstance also has a null symbol geometry, I cannot really think of anything more that can be done. Obviously, it would make sense to improve the robustness of the CmdGetMaterials so that is can handle these cases gracefully and not crash, but I do not know any other way to access the element geometry if all these paths return nothing useful. Sorry about that.
Cheers, Jeremy.
Posted by: Jeremy Tammik | June 22, 2009 at 06:17
Thanks for the help!
However I'm not disturbed with the crash. I simply wan't to get the geometry of those elements in one or other way and I can't :(
"Simply need to be skipped" Can you explain why?
Let's suppose that I'm writing a plugin for Revit that is exporting the geometry of the model elements to a third party application. Everything works great except the case when I get some of these strange elements which I cannot handle. Any idea how to get the geometry instead skipping them? I just tried to export that model as IFC and it works great, I mean the element is visible in my IFC viewer.
Cheers,
Andras
Posted by: Andras Kiss | June 22, 2009 at 08:15
Dear Andras,
When I say "simply need to be skipped", all I mean is that there is nothing to get there through the API, so we might as well skip them.
For railings, there is no corresponding class in the API to expose their geometry.
For furniture and plants, you may be able to explore the geometry using the 2010 family API. You can let me know if you have a specific example which does not provide any geometry.
Regarding the IFC export, I am sorry to say that there is no correlation at all between what the IFC export does and what is in the Revit API.
Cheers, Jeremy.
Posted by: Jeremy Tammik | June 22, 2009 at 15:39
Thanks Jeremy,
That is sad :( We have our Revit plugin for 2009 and also for 2010. In this case your first solution is not viable for 2009. Can I send you a little file to see what am I talking about?
Cheers, Andras
Posted by: Andras Kiss | June 23, 2009 at 02:07
Dear Andras,
Yes, sure, I sent you a separate email.
Cheers, Jeremy.
Posted by: Jeremy Tammik | June 23, 2009 at 02:13
Dear Andras,
So after taking a look at your sample file, we determined some characteristics of the problematic elements.
The table family Craftsman02 is a very old family created by importing 3D DWG mesh data into a Revit family, so it is reasonable that there is no solid geometry that the Revit API can read, since there are no Revit solids in the family. It was created several releases ago, so note that there is no “Edit Family” button when this family is selected. Therefore it is questionable if this is a representative test case for what you actually want to achieve.
The GetMaterials method in the command CmdGetMaterials can be modified to something like this so it doesn’t crash in this case:
public List GetMaterials( GeoElement geo )
{
List materials = new List();
foreach( GeometryObject o in geo.Objects )
{
if( o is Solid )
{
Solid solid = o as Solid;
if( null != solid )
{
foreach( Face face in solid.Faces )
{
string s = face.MaterialElement.Name;
materials.Add( s );
}
}
}
else if( o is GeoInstance )
{
GeoInstance i = o as GeoInstance;
foreach( Object geomObj in i.SymbolGeometry.Objects )
{
Solid solid = geomObj as Solid;
if( solid != null )
{
foreach( Face face in solid.Faces )
{
string s = face.MaterialElement.Name;
materials.Add( s );
}
}
}
}
}
return materials;
}
Unlike the original implementation, this implementation is not recursive, so it will skip materials of deeply nested geo instances. It will only examine the first level of geo instances and then stop.
Cheers, Jeremy.
Posted by: Jeremy Tammik | June 24, 2009 at 02:11
Hi Jeremy,
I'm trying to get the materials from a family. I allow the user to pick the items and then I'm going through each one and I can grab their parameters but I can't seem to get their values.
'Select your objects to move/rotate
Dim elemDeleteList As New List(Of ElementId)()
Dim eRefList As IList(Of Reference) = m_document.Selection.PickObjects(Autodesk.Revit.UI.Selection.ObjectType.Element, "Please pick some element(s) to Move and Rotate. ESC for Cancel.")
For Each eRef As Reference In eRefList
If eRef IsNot Nothing AndAlso eRef.Element IsNot Nothing Then
Dim instance As FamilyInstance = eRef.Element
For Each param As Parameter In instance.Parameters
Dim paramName As String = param.Definition.Name
Dim paramValue As String = param.AsValueString
MsgBox(paramName & " = " & paramValue)
Next
End If
Next
I can get through all the materials this way but I need to match up the name with the actual material.
For Each Mat As Material In eRef.Element.Materials
MsgBox(Mat.Name)
Next
I've been looking around on the internet and the API Help and just haven't gotten it figured out yet. Any help would greatly be appreciated.
Thanks!
Matt
Posted by: Matt Landress | July 23, 2010 at 15:46
Dear Matt,
You can query the database value of parameters using different methods depending on the parameter type:
Parameter p = ...
int i = p.AsInteger();
double x = p.AsDouble();
string s = p.AsString();
You have to use the appropriate method depending on the type of value stored in the parameter, which can be determined by querying its StorageType.
AsValueString returns the value displayed in the user interface, and sometimes nothing at all, I think.
You can use a dictionary to map the name of a material to a data holder populated with the values that you are interested in.
Cheers, Jeremy.
Posted by: Jeremy Tammik | July 26, 2010 at 04:52
Can I get if the material is applied as paint on the face?
Posted by: Scott | April 02, 2012 at 15:47
Dear Scott,
I am afraid I cannot answer this off-hand. It requires some exploration of a sample model. The best tools for this are the element lister and RevitLookup. There are a number of blog posts describing how I used them to search for various parameters. Basically, the approach is always the same: Create a new model. Add an element. List all elements using the element lister. Make a modification. List all elements again using the element lister. Determine the differences. Explore the differences in more detail using RevitLookup. Find out what it is you are looking for. The data is mostly hidden in some parameter somewhere. If not, chances are it is not available. I hope this helps.
Cheers, Jeremy.
Posted by: Jeremy Tammik | April 03, 2012 at 04:06