diff --git a/src/bonsai/bonsai/bim/module/spatial/operator.py b/src/bonsai/bonsai/bim/module/spatial/operator.py index dc68d95a8a..c3016f4cee 100644 --- a/src/bonsai/bonsai/bim/module/spatial/operator.py +++ b/src/bonsai/bonsai/bim/module/spatial/operator.py @@ -239,6 +239,7 @@ class ImportSpatialDecomposition(bpy.types.Operator): def execute(self, context): core.import_spatial_decomposition(tool.Spatial) + core.create_orientation_slots(ifc=tool.Ifc, spatial=tool.Spatial) return {"FINISHED"} @@ -345,6 +346,7 @@ class SetDefaultContainer(bpy.types.Operator): def execute(self, context): core.set_default_container(tool.Spatial, container=tool.Ifc.get().by_id(self.container)) + core.set_orientation_slot(spatial=tool.Spatial, element=tool.Ifc.get().by_id(self.container)) return {"FINISHED"} diff --git a/src/bonsai/bonsai/core/spatial.py b/src/bonsai/bonsai/core/spatial.py index eff50ec2a3..04ae08ad6f 100644 --- a/src/bonsai/bonsai/core/spatial.py +++ b/src/bonsai/bonsai/core/spatial.py @@ -121,6 +121,15 @@ def import_spatial_decomposition(spatial: tool.Spatial) -> None: spatial.import_spatial_decomposition() +def create_orientation_slots(ifc: tool.Ifc, spatial: tool.Spatial) -> None: + elements = ifc.get().by_type("IfcSpatialElement") + spatial.create_orientation_slots(elements, use=False) + + +def set_orientation_slot(spatial: tool.Spatial, element: ifcopenshell.entity_instance) -> None: + spatial.create_orientation_slots([element], use=True) + + def edit_container_attributes(spatial: tool.Spatial, entity: ifcopenshell.entity_instance) -> None: spatial.edit_container_attributes(entity) spatial.import_spatial_decomposition() diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index f2c784c464..9c0c3b758d 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -38,6 +38,7 @@ import bonsai.core.geometry import bonsai.core.unit import bonsai.tool as tool import json +import numpy as np from math import pi from mathutils import Vector, Matrix from shapely import Polygon @@ -479,6 +480,51 @@ class Spatial(bonsai.core.tool.Spatial): for child in children or []: cls.import_spatial_element(child, level_index + 1) + @classmethod + def create_orientation_slots(cls, elements: list[ifcopenshell.entity_instance], use: bool = False) -> None: + active_slot = bpy.context.scene.transform_orientation_slots[0] + initial_orientation = active_slot.type + + # stash selected objects + active_object = bpy.context.view_layer.objects.active + selected_objects = list(bpy.context.view_layer.objects.selected) + + # bpy.ops.transform.create_orientation() requires a dummy object + bpy.ops.object.empty_add(type="PLAIN_AXES") + + for element in elements: + placement = element.ObjectPlacement + combined_matrix = ifcopenshell.util.placement.get_local_placement(placement)[:3, :3] + if np.allclose(combined_matrix, np.eye(3), atol=1e-6): + # this element has global orientation + active_slot.type = "GLOBAL" + continue + elif element.Decomposes and hasattr(element.Decomposes[0].RelatingObject, "ObjectPlacement"): + # this element is part of a decomposition + parent_placement = element.Decomposes[0].RelatingObject.ObjectPlacement + parent_matrix = ifcopenshell.util.placement.get_local_placement(parent_placement)[:3, :3] + if np.allclose(combined_matrix, parent_matrix, atol=1e-6): + # this element has the same orientation as its parent + cls.create_orientation_slots(elements=[element.Decomposes[0].RelatingObject], use=use) + continue + # this element has a unique orientation + orientation_name = element.is_a() + "/" + element.Name + bpy.ops.transform.create_orientation(name=orientation_name, overwrite=True) + active_slot.type = orientation_name + active_slot.custom_orientation.matrix = np.linalg.inv(combined_matrix) + + # delete dummy object + bpy.ops.object.delete() + + # reinstate selected objects + for obj in selected_objects: + obj.select_set(True) + if active_object: + bpy.context.view_layer.objects.active = active_object + + if not use: + active_slot.type = initial_orientation + @classmethod def edit_container_attributes(cls, entity: ifcopenshell.entity_instance) -> None: # TODO