Annotation rotation when doing bulk tags (#7049)

* first attempt to do annotation rotation when dong bulk tags

* Enhance annotation rotation handling based on camera orientation

* handle properly S_G

* black .

* reorder property in UI

* cleanup imports
This commit is contained in:
falken10vdl
2025-10-22 06:07:52 +02:00
committed by GitHub
parent edd01d44de
commit bb3cd7e49c
3 changed files with 74 additions and 4 deletions
@@ -864,6 +864,19 @@ class BIMAnnotationProperties(PropertyGroup):
)
is_adding_type: bpy.props.BoolProperty(default=False)
type_name: bpy.props.StringProperty(name="Name", default="TYPEX")
tag_rotation_mode: bpy.props.EnumProperty(
name="Tag Rotation Mode",
description="How to orient the tag relative to the tagged object",
items=[
("NONE", "No Rotation", "Keep tag in default orientation"),
("LOCAL_X", "Local X Axis", "Align tag with object's local X axis"),
("LOCAL_Y", "Local Y Axis", "Align tag with object's local Y axis"),
("LOCAL_Z", "Local Z Axis", "Align tag with object's local Z axis"),
("CAMERA_Horizontal", "Camera Horizontal", "Align tag with camera X axis"),
("CAMERA_Vertical", "Camera Vertical", "Align tag with camera Y axis"),
],
default="NONE",
)
if TYPE_CHECKING:
object_type: str
@@ -228,7 +228,6 @@ class AnnotationToolUI:
def draw_type_selection_interface(cls):
# shared by both sidebar and header
object_type = cls.props.object_type
row = cls.layout.row(align=True)
row.label(text="", icon="FILE_VOLUME")
prop_with_search(row, cls.props, "object_type", text="")
@@ -248,6 +247,9 @@ class AnnotationToolUI:
add_layout_hotkey_operator(cls.layout, "Add", "S_A", "Create a new annotation")
if object_type in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP:
row = cls.layout.row(align=True)
row.label(text="", icon="DRIVER_ROTATIONAL_DIFFERENCE")
row.prop(cls.props, "tag_rotation_mode", text="")
add_layout_hotkey_operator(
cls.layout,
"Bulk Tag",
@@ -313,7 +315,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
),
enable_editing=False,
)
tool.Drawing.setup_annotation_object(obj, object_type, related_object)
tool.Drawing.setup_annotation_object(obj, object_type, related_object, props.tag_rotation_mode)
created_objects.append(obj)
# Select the created annotation objects
@@ -323,6 +325,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
if created_objects:
bpy.context.view_layer.objects.active = created_objects[-1]
def hotkey_S_A(self):
if bpy.ops.bim.add_annotation.poll():
bpy.ops.bim.add_annotation()
@@ -351,4 +354,5 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
continue
related_object = tool.Ifc.get_object(related_product)
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object)
rotation_mode = tool.Drawing.get_annotation_props().tag_rotation_mode
tool.Drawing.setup_annotation_object(obj, annotation_type, related_object, rotation_mode)
+54 -1
View File
@@ -251,7 +251,11 @@ class Drawing(bonsai.core.tool.Drawing):
@classmethod
def setup_annotation_object(
cls, obj: bpy.types.Object, object_type: str, related_object: Optional[bpy.types.Object] = None
cls,
obj: bpy.types.Object,
object_type: str,
related_object: Optional[bpy.types.Object] = None,
rotation_mode: str = "NONE",
) -> None:
"""Finish object's adjustments after both object and entity are created"""
@@ -323,6 +327,55 @@ class Drawing(bonsai.core.tool.Drawing):
ifc_file, relating_product=related_entity, related_object=obj_entity
)
if rotation_mode != "NONE" and related_object:
cls.apply_annotation_rotation(obj, related_object, rotation_mode)
@classmethod
def apply_annotation_rotation(
cls, tag_obj: bpy.types.Object, related_object: bpy.types.Object, rotation_mode: str
) -> None:
"""Apply rotation to annotation based on the selected rotation mode"""
camera = bpy.context.scene.camera
camera_right = camera.matrix_world.to_3x3() @ mathutils.Vector((1, 0, 0))
if rotation_mode == "CAMERA_Horizontal":
location = tag_obj.location.copy()
camera_matrix = camera.matrix_world.copy()
camera_matrix.translation = location
tag_obj.matrix_world = camera_matrix
elif rotation_mode == "CAMERA_Vertical":
location = tag_obj.location.copy()
camera_matrix = camera.matrix_world.copy()
camera_matrix.translation = location
rotation_90z = mathutils.Matrix.Rotation(math.pi / 2, 4, "Z")
camera_matrix = camera_matrix @ rotation_90z
tag_obj.matrix_world = camera_matrix
elif rotation_mode == "LOCAL_X":
local_x = related_object.matrix_world.to_3x3() @ mathutils.Vector((1, 0, 0))
local_x = local_x.normalized()
if local_x.dot(camera_right) < 0:
local_x = -local_x
tag_obj.rotation_euler = local_x.to_track_quat("X", "Z").to_euler()
elif rotation_mode == "LOCAL_Y":
local_y = related_object.matrix_world.to_3x3() @ mathutils.Vector((0, 1, 0))
local_y = local_y.normalized()
if local_y.dot(camera_right) < 0:
local_y = -local_y
tag_obj.rotation_euler = local_y.to_track_quat("X", "Z").to_euler()
elif rotation_mode == "LOCAL_Z":
local_z = related_object.matrix_world.to_3x3() @ mathutils.Vector((0, 0, 1))
local_z = local_z.normalized()
if local_z.dot(camera_right) < 0:
local_z = -local_z
tag_obj.rotation_euler = local_z.to_track_quat("X", "Z").to_euler()
@classmethod
def is_annotation_object_type(
cls, element: ifcopenshell.entity_instance, object_types: Union[str, Sequence[str]]