Implement faux-mirroring.

This commit is contained in:
Dion Moult
2023-03-27 14:21:59 +11:00
parent bdb193f500
commit 02fb09ff7e
5 changed files with 69 additions and 2 deletions
@@ -59,6 +59,7 @@ def register():
if wm.keyconfigs.addon:
km = wm.keyconfigs.addon.keymaps.new(name="Object Mode", space_type="EMPTY")
kmi = km.keymap_items.new("bim.override_object_duplicate_move", "D", "PRESS", shift=True)
kmi.properties.is_interactive = True
kmi = km.keymap_items.new("bim.override_object_duplicate_move_linked", "D", "PRESS", alt=True)
kmi = km.keymap_items.new("bim.override_paste_buffer", "V", "PRESS", ctrl=True)
kmi = km.keymap_items.new("bim.override_mode_set_edit", "TAB", "PRESS")
@@ -502,6 +502,7 @@ class OverrideDuplicateMove(bpy.types.Operator):
bl_idname = "bim.override_object_duplicate_move"
bl_label = "IFC Duplicate Objects"
bl_options = {"REGISTER", "UNDO"}
is_interactive: bpy.props.BoolProperty(name="Is Interactive", default=True)
@classmethod
def poll(cls, context):
@@ -528,7 +529,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
new_obj.select_set(True)
if new_active_obj:
context.view_layer.objects.active = new_active_obj
bpy.ops.transform.translate("INVOKE_DEFAULT")
if self.is_interactive:
bpy.ops.transform.translate("INVOKE_DEFAULT")
return {"FINISHED"}
def _execute(self, context):
@@ -552,7 +554,8 @@ class OverrideDuplicateMove(bpy.types.Operator):
old_to_new[tool.Ifc.get_entity(obj)] = [new]
# Recreate decompositions
tool.Root.recreate_decompositions(relationships, old_to_new)
bpy.ops.transform.translate("INVOKE_DEFAULT")
if self.is_interactive:
bpy.ops.transform.translate("INVOKE_DEFAULT")
blenderbim.bim.handler.purge_module_data()
@@ -52,6 +52,7 @@ classes = (
product.AlignProduct,
product.ChangeTypePage,
product.LoadTypeThumbnails,
product.MirrorElements,
workspace.Hotkey,
wall.AlignWall,
wall.ChangeExtrusionDepth,
@@ -398,6 +398,64 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
class MirrorElements(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.mirror_elements"
bl_label = "Mirror Elements"
bl_options = {"REGISTER", "UNDO"}
bl_description = "Faux-mirrors the selected object by an active empty along a mirror plane."
@classmethod
def poll(cls, context):
return context.selected_objects
def _execute(self, context):
# This is not a true mirror operation. In BIM, objects that are
# mirrored are not the same type. (i.e. a mirrored asymmetric desk is a
# completely different product). To preserve types, we calculate
# bounding box centroids, and mirror the position of the object. The
# mirrored object performs the necessary rotation to preserve the
# mirrored intention, but never actually truly mirror anything (i.e.
# scale = -1).
# Objects are mirrored along the YZ plane of the mirror object.
# Mirrored objects have their relative local Y and Z axes preserved,
# and the new X axis is calculated.
# In theory, untyped objects may be truly mirrored, but this is not yet
# implemented.
mirror = context.active_object
reflection = Matrix()
reflection[0][0] = -1
mirror.select_set(False)
bpy.ops.bim.override_object_duplicate_move(is_interactive=False)
for obj in context.selected_objects:
if obj == mirror:
continue
objmat = mirror.matrix_world.inverted() @ obj.matrix_world.copy()
x, y, z = objmat.to_3x3().col
centroid = Vector(obj.bound_box[0]).lerp(Vector(obj.bound_box[6]), 0.5)
c = mirror.matrix_world.inverted() @ obj.matrix_world @ centroid
newy = mirror.matrix_world.to_quaternion() @ (y @ reflection)
newz = mirror.matrix_world.to_quaternion() @ (z @ reflection)
newx = newy.cross(newz)
newc = mirror.matrix_world @ (c @ reflection)
newmat = Matrix((newx.to_4d(), newy.to_4d(), newz.to_4d(), newc.to_4d())).transposed()
newmat.col[3][0:3] = Vector(newmat.col[3][0:3]) - (newmat.to_quaternion() @ centroid)
obj.matrix_world = newmat
def copy_obj(self, obj):
new = obj.copy()
new.data = wall2.data.copy()
wall1.users_collection[0].objects.link(wall2)
blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=wall2)
def generate_box(usecase_path, ifc_file, settings):
box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW")
if not box_context:
@@ -368,6 +368,8 @@ class BimToolUI:
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Align Interior", icon="EVENT_V")
row = cls.layout.row(align=True)
row.label(text="", icon="EVENT_SHIFT")
row.label(text="Mirror", icon="EVENT_M")
row = cls.layout.row(align=True)
row.label(text="Mode")
@@ -606,6 +608,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
return
if self.active_material_usage == "LAYER2":
bpy.ops.bim.merge_wall()
else:
bpy.ops.bim.mirror_elements()
def hotkey_S_R(self):
if not bpy.context.selected_objects: