mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Implement faux-mirroring.
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user