From dff70f2608fd7af03280a2fa504d0c5df70dd192 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 4 Oct 2023 18:14:43 +0500 Subject: [PATCH] bim.flip_object #3803 Added new operator bim.flip_object that flips the object by flipping two of its axes and keeps the original placement. Currently it can be found either from F3 search or from Shift-F hotkey for IfcBeam and IfcColumn in BIM tools. --- .../bim/module/geometry/__init__.py | 1 + .../bim/module/geometry/operator.py | 16 +++++++++++++ .../blenderbim/bim/module/model/opening.py | 13 +--------- .../blenderbim/bim/module/model/workspace.py | 3 +++ src/blenderbim/blenderbim/tool/blender.py | 2 ++ src/blenderbim/blenderbim/tool/geometry.py | 24 ++++++++++++++++++- 6 files changed, 46 insertions(+), 13 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py index 29503856bc..1b64ef180e 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py @@ -23,6 +23,7 @@ classes = ( operator.AddRepresentation, operator.CopyRepresentation, operator.EditObjectPlacement, + operator.FlipObject, operator.GetRepresentationIfcParameters, operator.OverrideDelete, operator.OverrideDuplicateMove, diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 157366aeb7..ef8c3e865c 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1723,3 +1723,19 @@ class OverrideModeSetObject(bpy.types.Operator): if self.edited_objs: return context.window_manager.invoke_props_dialog(self) return self.execute(context) + + +class FlipObject(bpy.types.Operator): + bl_idname = "bim.flip_object" + bl_label = "Flip Object" + bl_description = "Flip object's local axes, keep the position" + bl_options = {"REGISTER", "UNDO"} + + flip_local_axes: bpy.props.EnumProperty( + name="Flip Local Axes", items=(("XY", "XY", ""), ("YZ", "YZ", ""), ("XZ", "XZ", "")), default="XY" + ) + + def execute(self, context): + for obj in context.selected_objects: + tool.Geometry.flip_object(obj, self.flip_local_axes) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index 9b841cfe2b..32f42340e9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -393,18 +393,7 @@ class FlipFill(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) if not element or not element.FillsVoids: continue - - flip_matrix = Matrix.Rotation(pi, 4, "Z") - - bottom_left = obj.matrix_world @ Vector(obj.bound_box[0]) - top_right = obj.matrix_world @ Vector(obj.bound_box[6]) - center = obj.matrix_world.translation.copy() - center_offset = center - bottom_left - flipped_center = top_right - center_offset - - obj.matrix_world = obj.matrix_world @ flip_matrix - obj.matrix_world.translation.xy = flipped_center.xy - bpy.context.view_layer.update() + tool.Geometry.flip_object(obj, "XY") return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 6af2600dee..0f8d59b683 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -358,6 +358,7 @@ class BimToolUI: cls.layout.operator("bim.mep_connect_elements") else: add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "") + add_layout_hotkey_operator(cls.layout, "Flip", "S_F", bpy.ops.bim.flip_object.__doc__) add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "") add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "") add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__) @@ -668,6 +669,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.bim.flip_wall() elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"): bpy.ops.bim.flip_fill() + elif self.active_class in ("IfcBeam", "IfcColumn"): + bpy.ops.bim.flip_object(flip_local_axes="XZ") elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"): bpy.ops.bim.fit_flow_segments() diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index d544a67787..73e245bdc9 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -343,6 +343,8 @@ class Blender: "max_y": bound_box[6][1], "min_z": bound_box[0][2], "max_z": bound_box[6][2], + "min_point": Vector(bound_box[0]), + "max_point": Vector(bound_box[6]), "center": (Vector(bound_box[6]) + Vector(bound_box[0])) / 2, } return bbox_dict diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 8e624d61f0..ce33fbcd7d 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -28,7 +28,7 @@ import blenderbim.core.style import blenderbim.core.spatial import blenderbim.tool as tool import blenderbim.bim.import_ifc -from math import radians +from math import radians, pi from mathutils import Vector, Matrix from blenderbim.bim.ifc import IfcStore @@ -641,3 +641,25 @@ class Geometry(blenderbim.core.tool.Geometry): @classmethod def get_model_representations(cls): return tool.Ifc.get().by_type("IfcShapeRepresentation") + + @classmethod + def flip_object(cls, obj, flip_local_axes): + assert len(flip_local_axes) == 2, "flip_local_axes must be two axes to flip" + rotation_axis = next(i for i in "XYZ" if i not in flip_local_axes) + rotation_axis_i = "XYZ".index(rotation_axis) + + bb_data = tool.Blender.get_object_bounding_box(obj) + # min max points of rotated plane of origin based bounding box + min_point = Vector([min(i, 0) for i in bb_data["min_point"]]) + max_point = Vector([max(i, 0) for i in bb_data["max_point"]]) + # keep it in rotated plane only + max_point[rotation_axis_i] = min_point[rotation_axis_i] + + # to compensate for flipped two axes + # we adjust new max point to match previous min point (or vice versa) + original_min_point = obj.matrix_world @ min_point + obj.matrix_world = obj.matrix_world @ Matrix.Rotation(pi, 4, rotation_axis) + new_max_point = obj.matrix_world @ max_point + obj.matrix_world.translation += original_min_point - new_max_point + + bpy.context.view_layer.update()