import bpy
import math
import bmesh
import mathutils
from math import radians
from bmesh.types import BMFace
from mathutils import Vector, Matrix, Euler
from bpy.props import IntProperty, FloatProperty, BoolProperty
from ... ui_framework.operator_ui import Master
from ... utility import addon
from ... utils.bmesh import get_verts_center
from ...utility.object import apply_transforms
class HOPS_OT_Mesh_Align(bpy.types.Operator):
bl_idname = "hops.mesh_align"
bl_label = "Align Mesh"
bl_description = "Align a mesh to an active face"
bl_options = {"REGISTER", "UNDO", "BLOCKING"}
obj_normal_offset: FloatProperty(name="Active normal offset", default=0, min=-20, max=20)
target_normal_offset: FloatProperty(name="Target normal offset", default=0, min=-20, max=20)
flip_edge_one: BoolProperty(name="Flip edge one", default=False)
flip_edge_two: BoolProperty(name="Flip edge two", default=False)
first_edge_solver: IntProperty(name="First Edge Solver", description="End user additional rotation solver", default=0, min=0, max=360)
second_edge_solver: IntProperty(name="Second Edge Solver", description="End user additional rotation solver", default=0, min=0, max=360)
apply_active_transform: BoolProperty(name="Apply Active Obj Transform", default=False)
apply_target_transform: BoolProperty(name="Apply Target Obj Transform", default=False)
place_cursor_at_pivot: BoolProperty(name="Place cursor at pivot", default=False)
called_ui = False
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
HOPS_OT_Mesh_Align.called_ui = False
@classmethod
def poll(cls, context):
selected_mesh_count = 0
for obj in context.selected_editable_objects:
if obj.type == 'MESH':
selected_mesh_count += 1
if selected_mesh_count > 1:
return True
return False
def execute(self, context):
if context.active_object.mode == 'OBJECT':
bpy.ops.object.mode_set(mode='EDIT')
# Get the two mesh objects
active_object = context.active_object
target_obj = None
for obj in context.selected_editable_objects:
if obj.type == 'MESH':
if obj.name != active_object.name:
target_obj = obj
break
if target_obj == None:
return {"FINISHED"}
if active_object and target_obj:
fin = align(
active_obj=active_object,
target_obj=target_obj,
obj_normal_offset=self.obj_normal_offset,
target_normal_offset=self.target_normal_offset,
first_edge_solver=self.first_edge_solver,
second_edge_solver=self.second_edge_solver,
flip_edge_one=self.flip_edge_one,
flip_edge_two=self.flip_edge_two,
apply_active_transform=self.apply_active_transform,
apply_target_transform=self.apply_target_transform,
place_cursor_at_pivot=self.place_cursor_at_pivot)
# Operator UI
if not HOPS_OT_Mesh_Align.called_ui:
HOPS_OT_Mesh_Align.called_ui = True
ui = Master()
draw_data = [
["Mesh Align"],
["Active N Offset", self.obj_normal_offset],
["Target N Offset", self.target_normal_offset],
["Flip edge one", self.flip_edge_one],
["Flip edge two", self.flip_edge_two],
["First Edge Solver", self.first_edge_solver],
["Second Edge Solver", self.second_edge_solver]]
if fin != None:
draw_data = [
["Mesh Align"],
[fin, "ERROR"]]
ui.receive_draw_data(draw_data=draw_data)
ui.draw(draw_bg=addon.preference().ui.Hops_operator_draw_bg, draw_border=addon.preference().ui.Hops_operator_draw_border)
return {"FINISHED"}
def align(
active_obj: bpy.types.Object,
target_obj: bpy.types.Object,
obj_normal_offset,
target_normal_offset,
first_edge_solver,
second_edge_solver,
flip_edge_one,
flip_edge_two,
apply_active_transform,
apply_target_transform,
place_cursor_at_pivot):
'''Align the active object using 3 verts to the target object using 3 verts.'''
if apply_active_transform:
apply_transforms(active_obj)
if apply_target_transform:
apply_transforms(target_obj)
# Active Object
a_mesh = active_obj.data
active_bm = bmesh.from_edit_mesh(a_mesh)
active_bm.normal_update()
# Target Object
t_mesh = target_obj.data
target_bm = bmesh.from_edit_mesh(t_mesh)
target_bm.normal_update()
# Active object first 3 verts
active_verts = [v for v in active_bm.select_history if isinstance(v, bmesh.types.BMVert)]
if len(active_verts) < 3:
return "Select 3 VERTS"
elif len(active_verts) > 3:
active_verts = active_verts[ 0:3 ]
# Target object first 3 verts
target_verts = [v for v in target_bm.select_history if isinstance(v, bmesh.types.BMVert)]
if len(target_verts) < 3:
return "Select 3 VERTS"
elif len(target_verts) > 3:
target_verts = target_verts[ 0:3 ]
verts = [v for v in active_bm.verts]
#-- Rotate first edge --#
active_edge = active_verts[1].co - active_verts[0].co
active_edge.normalize()
target_edge = target_verts[1].co - target_verts[0].co
target_edge.normalize()
angle = target_edge.angle(active_edge)
if flip_edge_one:
angle -= math.pi / 2
angle += math.radians(first_edge_solver)
axis = active_edge.cross(target_edge)
rot_mat = Matrix.Rotation(angle, 3, axis)
bmesh.ops.rotate(
active_bm,
cent=target_verts[0].co,
matrix=rot_mat,
verts=verts,
space=active_obj.matrix_world)
#-- Rotate second edge --#
a_edge_1 = (active_obj.matrix_world @ active_verts[1].co) - (active_obj.matrix_world @ active_verts[0].co)
a_edge_2 = (active_obj.matrix_world @ active_verts[2].co) - (active_obj.matrix_world @ active_verts[0].co)
a_normal = a_edge_1.cross(a_edge_2)
a_normal.normalize()
t_edge_1 = (target_obj.matrix_world @ target_verts[1].co) - (target_obj.matrix_world @ target_verts[0].co)
t_edge_2 = (target_obj.matrix_world @ target_verts[2].co) - (target_obj.matrix_world @ target_verts[0].co)
t_normal = t_edge_1.cross(t_edge_2)
t_normal.normalize()
normal_diff_quat = a_normal.rotation_difference(t_normal)
angle = t_normal.angle(a_normal)
if flip_edge_two:
angle -= math.pi / 2
angle += math.radians(second_edge_solver)
axis = target_verts[1].co - target_verts[0].co
rot_mat = Matrix.Rotation(angle, 3, axis)
bmesh.ops.rotate(
active_bm,
cent=target_verts[1].co - target_verts[0].co,
matrix=rot_mat,
verts=verts,
space=active_obj.matrix_world)
#-- Move --#
active_point_1 = active_obj.matrix_world @ active_verts[0].co
target_point_1 = target_obj.matrix_world @ target_verts[0].co
obj_offset_vec = target_point_1 - active_point_1
for vert in verts:
vert.co += obj_offset_vec
#-- Additional Offset Redo Panel --#
a_edge_1 = (active_obj.matrix_world @ active_verts[1].co) - (active_obj.matrix_world @ active_verts[0].co)
a_edge_2 = (active_obj.matrix_world @ active_verts[2].co) - (active_obj.matrix_world @ active_verts[0].co)
a_normal = a_edge_1.cross(a_edge_2)
a_normal.normalize()
offset = a_normal * obj_normal_offset
for vert in verts:
vert.co += offset
t_edge_1 = (target_obj.matrix_world @ target_verts[1].co) - (target_obj.matrix_world @ target_verts[0].co)
t_edge_2 = (target_obj.matrix_world @ target_verts[2].co) - (target_obj.matrix_world @ target_verts[0].co)
t_normal = t_edge_1.cross(t_edge_2)
t_normal.normalize()
offset = t_normal * target_normal_offset
for vert in verts:
vert.co += offset
if place_cursor_at_pivot:
bpy.context.scene.cursor.location = active_point_1 + obj_offset_vec
up = Vector((0,0,1))
angle = up.rotation_difference(t_normal).to_euler()
bpy.context.scene.cursor.rotation_euler = angle
# Update
bmesh.update_edit_mesh(a_mesh)
return None
def set_rotation_on_object(obj: bpy.types.Object):
euler = Euler(map(radians, (0, 0, 0)), 'XYZ')
loc, rot, scale = obj.matrix_world.decompose()
smat = Matrix()
for i in range(3):
smat[i][i] = scale[i]
mat = Matrix.Translation(loc) @ euler.to_matrix().to_4x4() @ smat
obj.matrix_world = mat