Hey @abevington,
Great question! To read the locations of sub-elements within walls (like windows, doors, electrical, etc.) and convert their positions to Cartesian coordinates, let’s dive into how hosted elements and RevitInstances
are structured in Speckle. I’ll provide a detailed explanation and some example code.
Understanding the Structure
-
Hosting Elements: Revit controls which elements can be hosted and how. You’ve mentioned walls, so I’ll focus on them here. Non-physical hosts can exist too and might be handled slightly differently.
-
Hosted Elements: All hosting elements have a property
elements
, a list of elements they host.
-
Examples:
-
Doors, Windows, etc.: Typically hosted
RevitInstance
s as FamilyTypes inserted into the wall.
-
Generic Elements: Sometimes walls may host non-instanced elements, which will have absolute coordinate values.
-
Curtain Wall Assemblies: Special case for curtain walls, not covered here.
RevitInstances
Many hosted elements are RevitInstances
. In Speckle, RevitInstances
are represented by two objects: the Instance
and the Definition
.
-
Instance: Records transform information and instance data parameters.
-
Definition: Contains the geometry and family/type data parameters.
The definition
property links to the definition object, which is resolved when using our SDK data retrieval operations.
Example Code
I often explore Speckle data and use this method to ease that while in the exploration phase.
def get_properties(element: Base) -> dict:
properties = {}
for key in element.get_dynamic_member_names():
properties[key] = getattr(element, key)
for key in element.get_member_names():
properties[key] = getattr(element, key)
return properties
Assuming you have a method to find all walls in the Speckle version data::
# objects is a flat list produced from the version commit data
walls = [obj for obj in objects if get_properties(obj).get("category") == "Walls"]
You’ve said you can get your positional information for Walls already, so, let’s extract hosted elements and include parent wall type and ID for context:
hosted_elements_with_parent_wall = [
{
**get_properties(hosted_element),
"parent_id": obj.id,
"parent_speckle_type": obj.speckle_type,
}
for obj in walls
for hosted_element in getattr(obj, 'elements', [])
]
If we analysed the types of all these elements, we may get:
instance_type = "...Objects.Other.Revit.RevitInstance"
openings_type = "...Objects.BuiltElements.Revit.RevitWallOpening"
curtain_wall_panel_type = "...Objects.BuiltElements.Revit.RevitCurtainWallPanel"
elements_type = "...Objects.BuiltElements.Revit.RevitElement"
Filter for RevitInstance
types:
hosted_elements_with_parent_wall = [
element for element in hosted_elements_with_parent
if element['speckle_type'] == instance_type
]
This step is crucial for discovering the local coordinates of the hosted elements and comparing them to global Cartesian coordinates. Each RevitInstance
element has an associated transformation matrix stored in its transform
property. This matrix is a 4x4 matrix used to perform transformations such as translation, rotation, and scaling. First, we extract the transformation matrix from each RevitInstance
element. This matrix is stored as a flat list of 16 values. It will be used to calculate the global coordinates.
Extract the transformation matrix for global coordinates:
# Augment with transformation matrix and extract Cartesian coordinates
revit_instance_elements = [
{**element, "transform_matrix": getattr(element.get("transform"), "matrix")}
for element in revit_instance_elements
]
Or label them as x, y, z coordinates:
revit_instance_elements = [
{
**element,
"x": element["transform_matrix"][3],
"y": element["transform_matrix"][7],
"z": element["transform_matrix"][11],
}
for element in revit_instance_elements
]
# Print the first element to verify
print(revit_instance_elements[0])
This transform information can create a global position for any nested geometries in a definition object. For example, Line start and ends * transform = global start and end coordinates.
Final Notes
This isn’t a complete picture. Additional positional information, like ‘mirrored’ boolean, is recorded in the RevitInstance, and rotational and scaling information is stored in the transform matrix but not used here. Nested hosted elements also need compounded transforms for accurate global positions.
Nested hosted elements are also not covered here. Transforms may need to be compounded to get the singular transform for a gand-child definition into a global position.
Instead of using a flatten function assumed here, which may return a list that includes the hosted elements in the big list and is retained as elements
per hosting element. Your data analysis will need to account for this. Alternatively, it is possible to devise graph traversal rules to compound these steps and work on the data in situ - it depends on your use-case intentions.
You may want to look at the Speckle Automate simple clash detection example, which applies the chain of transforms to get transformed display mesh geometry… However, if you are seeking positional information, this is not necessary.