Create transform orientation slots #6128

Blender supports named UCS coordinate systems. Refreshing the spatial
decomposition panel now creates orientation slots for each unique
spatial element orientation.

Additionally, setting the default spatial container selects an
appropriate orientation slot. eg. if a building is rotated, selecting to
work in a storey will set the orientation to the building orientation.

These slots are only created/updated in the spatial decomposition panel,
maybe they should also be created when loading the file and other
places.

The blender API doesn't currently allow listing custom orientation slots
for deletion, so if a spatial element is deleted or renamed, the old
orientation slot will persist for the rest of the session.
This commit is contained in:
Bruno Postle
2025-02-23 13:29:33 +00:00
parent b93cbbf989
commit 62bd495a9e
3 changed files with 57 additions and 0 deletions
@@ -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"}
+9
View File
@@ -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()
+46
View File
@@ -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