Is there a way to rotate bm.verts around a given point (another vertex, cursor etc) by multiplying v.co with Marix.Rotation and not using bmesh.ops.rotate? Like this:
for i, v in self.vco.items():
bm.verts[i].co = rot @ v
# vco is my dictionary of selected vertices
# rot is my Matrix.Rotation
can go into more detail why I want to avoid bmesh.ops.rotate, but in my case due to the Modal Operator I'm trying ot create I would like the MOUSEMOVE event to manipulate vertex coordinates directly. I don't want bmesh.ops.rotate to be triggered every time the mouse moves. However, just multiplying vertex coordinates with Matrix.Rotation will rotate the selected vertices around object origin. I need to rotate around a given point. I know bmesh.ops.rotate has the cent attribute which will allow you to specify the center of rotation. Can I do some Matrix math and avoid bmesh.ops.rotation? Or is there a way to use bmesh.ops.rotation with a Modal Operator and MOUSEMOVE event without the thing going crazy?
Edit: I've cleaned up what I have so far. Would it be possible for you to show how to either avoid exponential rotation (the thing going crazy) with bmesh.ops.rotate or how to modify the matrix math so that rotation will pivot around active edge instead of object origin?
# Usage instructions:
# 1) Copy and paste into Blender 2.8 text editor.
# 2) Press Run script in the text editor.
# 3) Create cube.
# 4) Go into edit mode.
# 5) Select one face.
# 6) With the face selected, go into Edge Selection mode and Shift-Double-Click on an edge to make it active. This will be the hinge that the selected face will rotate around.
# 7) Use the Search Menu to run the command Test Rotate.
# Within the Modal function I'm including two ways of rotating - with bmesh.ops.rotate and by matrix (commented out the for loop for now). I have different issues with both. In the case of bmesh.ops.rotate I don't know how to avoid rotation building up exponentially on the Mousemove event. In the case of rotating selected verts by matrix, the angle of rotation is correct but the rotation hinge is at the object origin, not the selected edge, and I don't know the matrix math to fix that.
import bpy
from bpy.props import IntProperty, FloatProperty
import math
from mathutils import Matrix
import bmesh
class ModalOperator(bpy.types.Operator):
"""Move an object with the mouse, example"""
bl_idname = "object.modal_z01ks"
bl_label = "Test Rotate"
def __init__(self):
print("Start")
def __del__(self):
print("End")
first_mouse_x = IntProperty()
first_value = FloatProperty()
global delta
delta = IntProperty()
def modal(self, context, event):
if event.type == 'MOUSEMOVE':
delta = self.first_mouse_x - event.mouse_x
obj = bpy.context.edit_object
me = obj.data
bm = bmesh.from_edit_mesh(me)
verts_selected = [v for v in bm.verts if v.select]
# Define edge variable from active edge, define axis from that edge, define rotation matrix from the axis
edge = bm.select_history.active
axis_rot = (edge.verts[0].co - edge.verts[1].co).normalized()
rot = Matrix.Rotation(math.radians(delta), 4, axis_rot)
# Check if there is an active edge
is_edge = isinstance(bm.select_history.active, bmesh.types.BMEdge)
if not is_edge:
self.report({'WARNING'}, "Please select Active Edge.")
print("Please select Active Edge.")
return {'CANCELLED'}
#---------------------------
# If we were to use the following for loop instead of bm.ops.rotate right below it, we would get nice incremental rotation on MOUSEMOVE, instead of exponential rotation. However, it would rotate the selected verts around object origin, not active edge. I still need to figure out the matrix math to rotate around active edge. Uncomment the for loop here and comment the line with bmesh.ops.rotate to see the difference.
#for i,v in self.vco.items():
# bm.verts[i].co = rot @ v
#---------------------------
bmesh.ops.rotate(bm, cent=edge.verts[0].co, matrix=rot, verts=verts_selected) # comment this line and uncomment the for loop above to see different behavior
me.update()
bmesh.update_edit_mesh(me)
elif event.type == 'LEFTMOUSE':
return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
context.object.location.x = self.first_value
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
# coordinates for selected vertices are added to a dictionary, to be used in conjunction with the for loop and rotating vertices by matrix above
self.vco=dict()
for v in bpy.context.object.data.vertices:
if v.select:
self.vco[v.index]=v.co
self.first_mouse_x = event.mouse_x
self.first_value = context.object.location.x
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def register():
bpy.utils.register_class(ModalOperator)
def unregister():
bpy.utils.unregister_class(ModalOperator)
if __name__ == "__main__":
register()
self.vco[v.index] = v.co.copy()
Can also use a vert as an indexself.vco[v] = v.co.copy()
The same applies withv = ob.location
if you changeob.location
you changev
. $\endgroup$self.vco
to reset the coords before the rotate, which is applied and seen "live" via the bmesh. (Not sure if you need to use mesh update every time). If the op is finished use the rotation, if not reset mesh to how it was from whence it started. Oh and I think the bmesh operators are cleaner codewise.bmesh.ops.delete(...)
is often an exception to the rule. $\endgroup$