5
$\begingroup$

I have an object already rotated on X,Y axis and I'm trying to find a solution to use the value of a slider to rotate this object on it's 'Z' local axis.

I've made an little script to explain my problem. You will have the create a new scene, rotate the default cube using RR on all axis, then use the script :

import bpy
from bpy.props import FloatProperty
from mathutils import Vector, Matrix, Quaternion, Euler
from math import degrees, radians

class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout

        obj = context.object

        row = layout.row()
        row.label(text="Active object is: " + obj.name)
        row = layout.row()
        row.prop(obj, "name")

        row = layout.row()
        row.prop(context.window_manager, "rotate")

def get_rotation(self):
    try:
        rotate = self["rotate"]
        #-- Here, the cube is always spinning...
        #obj = bpy.context.object
        #obj.matrix_world *= Matrix.Rotation(radians(self["rotate"]), 4, 'Z')
    except KeyError:
        self["rotate"] = 0
    return self["rotate"]


def set_rotation(self, value):
    self["rotate"] = value
    #-- Here, it's working at the beginning, but fail when going toward 0.
    obj = bpy.context.object
    obj.matrix_world *= Matrix.Rotation(radians(self["rotate"]), 4, 'Z')

def update_rotation(self, context):
    pass
    #-- Here, it's working at the beginning, but fail when going toward 0.
    #obj = bpy.context.object
    #obj.matrix_world *= Matrix.Rotation(radians(self["rotate"]), 4, 'Z')

def register():
    bpy.types.WindowManager.rotate = FloatProperty(name="Rotation", description="Rotate object.", 
                                                    get=get_rotation, set=set_rotation,
                                                    update=update_rotation)
    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    del bpy.types.WindowManager.rotate
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()

As you can see, when using the script, the value of the slider will be add to the rotation, instead I would like the rotation to match the value of the slider. I've tried with an update and the getter/setter, but i failed to find a solution. I don't know if it's possible without an operator. thanks a lot for your help.

$\endgroup$
  • 1
    $\begingroup$ IIRC I tried something similar to this For a pitch / roll / yaw panel with limited success. As usual with Eulers come up against gimbal lock, where another axis offset rotation solution prevails. Would need some way to check for the "jump". $\endgroup$ – batFINGER Sep 10 '18 at 14:49
  • $\begingroup$ Thanks a lot batFINGER ! It's exactly what i was looking for. Really nice and easy to understand. $\endgroup$ – Clarkx Sep 10 '18 at 15:11
1
$\begingroup$

By using obj.matrix_world *= Matrix(...) you apply the rotation on top of the existing matrix, which explains the behaviour you're seeing.

Rather than changing the matrix, why not go to the source of the rotation itself, the rotation_euler property? You can just do obj.rotation_euler.z = radians(self["rotate"]) and let Blender to do the matrix math for you.

Alternatively you could use a driver that reads that particular window manager property.

$\endgroup$
  • $\begingroup$ I tried it, but I need to rotate the object on it's local axis. Using rotation_euler.z doesn't work if the cube is already rotated on the x, y axis. $\endgroup$ – Clarkx Sep 10 '18 at 12:11
  • 1
    $\begingroup$ @Clarkx Are you after the effect of pressing RZZ ? (Whereas your script appears to rotate globally about Z.) The eulers are the combination of multiplying each axis rotation in an order. Changing the order (eg ZYX) will effect which one is a single (only changes one value when rotating) axis of revolution with the others non zero. For this case using the local rotation properties as outlined in answer would suffice IMO. Otherwise maybe converting from one order to the other to have consistent window manager prop could be a way to go about it. $\endgroup$ – batFINGER Sep 10 '18 at 13:21
  • 1
    $\begingroup$ Yes, that's exactly what I want. I've edited my problem to explain exactly what i need. Sorry if it was not clear enough. Regarding your answer batFINGER, if I change the rotation_mode to 'ZYX', this will change my local axis accordingly. Thanks to both of you to taking the time ! $\endgroup$ – Clarkx Sep 10 '18 at 14:32
0
$\begingroup$

Try Changing the line:

obj.matrix_world *= Matrix.Rotation(radians(self["rotate"]), 4, 'Z')

to

obj.matrix_world = Matrix.Rotation(self["rotate"], 4, 'Z')#input in radians

or

obj.matrix_world = Matrix.Rotation(radians(self["rotate"]), 4, 'Z')#input in degrees
$\endgroup$
  • 1
    $\begingroup$ This would lose any translation or scale that was there. $\endgroup$ – dr. Sybren Sep 10 '18 at 14:29
  • $\begingroup$ yes, and i have tried to decomposed the matrix, but it will end with the same problem, thanks anyway $\endgroup$ – Clarkx Sep 10 '18 at 14:41
0
$\begingroup$

I'm copy/pasting the solution from @batFINGER so I will be able to change this problem to solved. Thanks.

import bpy
from bpy.props import FloatProperty
from mathutils import Vector, Matrix, Quaternion, Euler
from math import degrees, radians

class HelloWorldPanel(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Hello World Panel"
    bl_idname = "OBJECT_PT_hello"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "object"

    def draw(self, context):
        layout = self.layout

        obj = context.object

        row = layout.row()
        row.label(text="Active object is: " + obj.name)
        row = layout.row()
        row.prop(obj, "name")

        row = layout.row()
        row.prop(obj, "rotate")

def get_rotation(self):
    return self.rotation_euler.to_matrix().to_euler('ZYX').z

def set_rotation(self, value):
    rot = self.rotation_euler.to_matrix().to_euler('ZYX')
    rot.z = value
    self.rotation_euler = rot.to_matrix().to_euler(self.rotation_mode)

def register():
    bpy.types.Object.rotate = FloatProperty(name="Rotation",
            description="Rotate object.",
            get=get_rotation,
            set=set_rotation,
            unit='ROTATION')

    bpy.utils.register_class(HelloWorldPanel)


def unregister():
    del bpy.types.Object.rotate
    bpy.utils.unregister_class(HelloWorldPanel)


if __name__ == "__main__":
    register()
$\endgroup$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.