1
$\begingroup$

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()
$\endgroup$
  • 1
    $\begingroup$ It's my understanding there would be very little difference between using the bmesh rotate operator Example and multiplying vertex coordinates individually by a rotation matrix. $\endgroup$ – batFINGER Mar 7 at 8:55
  • 1
    $\begingroup$ This answer and the one linked to it show how to emulate a rotation operator, at an object level, including the matrix math to translate to axis, rotate, translate back. Speculate that what went "crazy" was continuously applying a rotation matrix on mousemove, as opposed to setting an absolute value from some reference eg up is the y axis of the 2d view. $\endgroup$ – batFINGER Mar 7 at 8:55
  • 1
    $\begingroup$ for a static copy of vert coodinates use self.vco[v.index] = v.co.copy() Can also use a vert as an index self.vco[v] = v.co.copy() The same applies with v = ob.location if you change ob.location you change v. $\endgroup$ – batFINGER Mar 7 at 11:56
  • 1
    $\begingroup$ Recommend use 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$ – batFINGER Mar 7 at 12:04
  • 1
    $\begingroup$ Creating geometry (extruding) adds a degree of complexity. Broken link in comment above Notice a vector is used to get the delta. Recommend answering this question first. Getting hard to follow where you are at with it. The extrusion part can be a follow up question. Off top of my head consider rotating the object about the global vector of the edge in operator. Locals will be that of original till applied.. Apply the delta rotation to mesh and re-adjust the mw of the object on completion or reset on cancel. $\endgroup$ – batFINGER Mar 18 at 16:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.