Location of sub elements in walls

Hey,

I am trying to create a project which needs to read in the locations of walls and the exact locations of the sub elements in walls (windows, doors, electrical, etc.) by their points in the Cartesian plane. I have gotten the walls fairly easily with [‘start’][‘x’] and so on for the other points but don’t know where the location data is stored for the sub elements and would love some help in figuring out how to get the cartesian points of windows and so on. I have included some screenshots of the data for a window below if that helps.



1 Like

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

  1. 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.
  2. Hosted Elements: All hosting elements have a property elements, a list of elements they host.
  3. Examples:
    • Doors, Windows, etc.: Typically hosted RevitInstances 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.

4 Likes

Thank you so much for the detailed answer. This helps a lot