Rotation of IfcElements based on its world center point instead of local placement at (0.0.0)

edited May 2024 in General

Hello all!

I'm trying to re-create geometry - in this case a wall and trying to rotate it to its initial position.
The problem is, the rotation is always conducted at (0.0.0) and not at the newly assigned location:

# Create our element type. Types do not have an object placement.
element = ifcopenshell.api.run("root.create_entity", ifc_file, ifc_class=element_type)

# Create a 4x4 identity matrix. This matrix is at the origin with no rotation.
matrix = np.eye(4)

# Relocate center point
center_point = center_point_x - (length/2), center_point_y - (width/2), center_point_z - (height/2)

# Set the X, Y, Z coordinates. Notice how we rotate first then translate.
# This is because the rotation origin is always at 0, 0, 0.
matrix[:,3][0:3] = center_point

# Set the rotation matrix
matrix[0, :3] =+ length_norm_vec
matrix[1, :3] =+ width_norm_vec
matrix[2, :3] =+ height_norm_vec

# Set our wall's Object Placement using our matrix.
run("geometry.edit_object_placement", ifc_file, product=element, matrix=matrix)

# Add a new wall-like body geometry
representation = run("geometry.add_wall_representation", ifc_file, context=body, length=length, height=height, thickness=width)

# Assign our new body geometry back to our wall
run("geometry.assign_representation", ifc_file, product=element, representation=representation)

I got an error when trying to rotate using ifcopenshell.util.placement.rotation:

# Rotate the matix 90 degrees anti-clockwise around the Z axis (i.e. in plan).
# Anti-clockwise is positive. Clockwise is negative.
matrix = ifcopenshell.util.placement.rotation(90, "Z") @ matrix

AttributeError: module 'ifcopenshell.util.placement' has no attribute 'rotation'
- ifcopenshell version: 0.8.0alpha1 - py311h9a73951_1

I'm working on a project which uses **open3d **in order to create ifcElements/Geometry from a point cloud.
This is very similiar to Innas' StackOverflow question:
She described it pretty clear:

"I'm working on a project where I need to apply rotation matrices obtained from Open3D to objects represented in IFC OpenShell. As per the documentations for the two: in Open3D, the rotation matrix represents rotation relative to the object's center, while in IFC OpenShell, rotation is defined based on two vectors: the local X axis and the local Z axis vector. I'm facing challenges in reconciling these differences and correctly applying the rotations.
Could someone provide guidance or code examples on how to convert rotation matrices from Open3D to the representation expected by IFC OpenShell, and how to apply these rotations to objects in IFC OpenShell?
Right now I am using the below code to extract the rotation matrix for a point cloud and then using the obtained parameters to create a wall like representation in IFC Open Shell. Here we're writing the rotation matrix to a txt file for simplicity.

In Open3D:

Code to extract the rotation matrix from the point cloud and save it in a file.
obb = point_cloud.get_oriented_bounding_box()
rotation_matrix = obb.R
min_x, min_y, min_z = obb.get_min_bound()
transformation_matrix = np.hstack((rotation_matrix, np.array([[min_x], [min_y], [min_z]])))
length = obb.extent[0]
width = obb.extent[1]
height = obb.extent[2]

f.write(f"{length}, {width}, {height}, ")
f.write(", ".join(map(str, transformation_matrix.flatten())))
f.write("\n")

In IFC OpenShell:

wall = run("root.create_entity", model, ifc_class="IfcWall")
run("geometry.edit_object_placement", model, product=wall, matrix=transformation_matrix)
representation = run("geometry.add_wall_representation", model, context=body, length=length, height=height, thickness=0.5)
run("geometry.assign_representation", model, product=wall, representation=representation)
run("spatial.assign_container", model, relating_structure=storey, product=wall)

Where: matrix: 4x4 transformation matrix for object placement length: Length of the wall height: Height of the wall thickness: Thickness of the wall"

Comments

  • AttributeError: module 'ifcopenshell.util.placement' has no attribute 'rotation'

    This means you're using an outdated version of IfcOpenShell. Can you try the latest from PyPI (or ideally from source)? See https://docs.ifcopenshell.org/ifcopenshell-python/installation.html

  • Okay nice, after updating the ifcopenshell version the code: matrix = ifcopenshell.util.placement.rotation(90, "Z") @ matrix is working. Do you suggested to continue with this approach? What is the difference between: matrix = ifcopenshell.util.placement.rotation(90, "Z") @ matrix and setting the rotation matrix?

    # Set the rotation matrix
    matrix[0, :3] = length_norm_vec
    matrix[1, :3] = width_norm_vec
    matrix[2, :3] = height_norm_vec
    
    # Set our wall's Object Placement using our matrix.
    run("geometry.edit_object_placement", ifc_file, product=element, matrix=matrix)
    

    To visualize the problem, I took a screenshot:

    The geometries are not identical..

  • It's getting closer, but not perfect at all.
    Here my code for testing:

    import ifcopenshell
    import ifcopenshell.geom
    import ifcopenshell.util
    from ifcopenshell.api import run
    import numpy as np
    from scipy.spatial.transform import Rotation as R
    
    space_list = [[5.45982203144534, 10.129900758998977, 4.856596703276808, 0.4895565961803325, 0.4470101884350913, 0.748676318960037, -0.8716389752888971, 0.2745826721999492, 0.406017059844604, -0.024079781843870243, -0.8513437892900608, 0.5240552609635669, 3.996563964906823, 3.015673581981595, 4.990903797214497], [5.462282313534009, 7.679392165854256, 10.824340423964122, -0.3912831219913468, 0.9023688972534317, -0.18063192329798877, 0.33371064841196946, 0.32204966908017146, 0.885957794582685, 0.8576332092340919, 0.2863815355692083, -0.4271427097632061, 4.338576617263058, 4.10136333547697, 2.2000317397514966]]
    space_list_count = 2
    
    def create_IFC_entity(ifc_file, geom_list, geom_counter, body, storey, element_type):
        for i in range(geom_counter):
            # Allocate input
            center_point_x = float(geom_list[i][0])
            center_point_y = float(geom_list[i][1])
            center_point_z = float(geom_list[i][2])
            length_norm_vec = geom_list[i][3:6]
            width_norm_vec = geom_list[i][6:9]
            height_norm_vec = geom_list[i][9:12]
            length = geom_list[i][-3]
            width = geom_list[i][-2]
            height = geom_list[i][-1]   
    
            # Create an element type. Types do not have an object placement.
            element = run("root.create_entity", ifc_file, ifc_class=element_type)
    
            # Add a new wall-like body geometry
            representation = run("geometry.add_wall_representation", ifc_file, context=body, length=length, height=height, thickness=width)
    
            # Assign new body geometry to element
            run("geometry.assign_representation", ifc_file, product=element, representation=representation)
    
            # Create a 4x4 identity matrix. This matrix is at the origin with no rotation.
            matrix = np.eye(4)
    
            # Set the rotation matrix
            matrix[0, :3] = length_norm_vec
            matrix[1, :3] = width_norm_vec
            matrix[2, :3] = height_norm_vec
    
            # Extract the rotation part of the matrix
            rotation_matrix = matrix[:3, :3]
    
            # Convert to Euler angles
            r = R.from_matrix(rotation_matrix)
            euler_angles = r.as_euler('xyz', degrees=True)
    
            euler_x, euler_y, euler_z = euler_angles
    
            # Apply rotations
            matrix = ifcopenshell.util.placement.rotation(euler_x, "X") @ matrix
            matrix = ifcopenshell.util.placement.rotation(euler_y, "Y") @ matrix
            matrix = ifcopenshell.util.placement.rotation(euler_z, "Z") @ matrix
    
            # Relocate center point
            center_point = center_point_x - (length/2), center_point_y - (width/2), center_point_z - (height/2)
    
            # Set the X, Y, Z coordinates. Notice how we rotate first then translate.
            # This is because the rotation origin is always at 0, 0, 0.
            matrix[:,3][0:3] = center_point
    
            # Set our wall's Object Placement using our matrix.
            run("geometry.edit_object_placement", ifc_file, product=element, matrix=matrix)
    
            if element_type == 'IfcSpace':
                # Place our wall in the ground floor
                run("aggregate.assign_object", ifc_file, relating_object=storey, products=[element])
    
            else: 
                # Place our wall in the ground floor
                run("spatial.assign_container", ifc_file, relating_structure=storey, products=[element])
    
            # Assign a name to the wall
            element.Name = f"{element_type}_Name_{str(i)}"
    
    
    # Create a blank model    
    ifc_file = ifcopenshell.file()
    
    # All projects must have one IFC Project element
    project = run("root.create_entity", ifc_file, ifc_class="IfcProject", name="My Project")
    
    # Geometry is optional in IFC, but because we want to use geometry in this example, let's define units
    # Assigning without arguments defaults to metric units
    run("unit.assign_unit", ifc_file, length={"is_metric": True, "raw": "METERS"})
    
    # Let's create a modeling geometry context, so we can store 3D geometry (note: IFC supports 2D too!)
    context = run("context.add_context", ifc_file, context_type="Model")
    
    # In particular, in this example we want to store the 3D "body" geometry of objects, i.e. the body shape
    body = run("context.add_context", ifc_file, context_type="Model",
        context_identifier="Body", target_view="MODEL_VIEW", parent=context)
    
    # Create a site, building, and storey. Many hierarchies are possible.
    site = run("root.create_entity", ifc_file, ifc_class="IfcSite", name="My Site")
    building = run("root.create_entity", ifc_file, ifc_class="IfcBuilding", name="Building A")
    storey = run("root.create_entity", ifc_file, ifc_class="IfcBuildingStorey", name="Ground Floor")
    
    # Since the site is our top level location, assign it to the project
    # Then place our building on the site, and our storey in the building
    run("aggregate.assign_object", ifc_file, relating_object=project, products=[site])
    run("aggregate.assign_object", ifc_file, relating_object=site, products=[building])
    run("aggregate.assign_object", ifc_file, relating_object=building, products=[storey])
    
    create_IFC_entity(ifc_file, space_list, space_list_count, body, storey,  "IfcWall")
    
    # Get current dir
    current_directory = os.getcwd()
    
    # Write out to a file
    ifc_file.write(f"{current_directory}/model.ifc")
    

  • this is how it looks

Sign In or Register to comment.