8
$\begingroup$

I have read so much about Blender rotation in the last two hours that my head is spinning, but I can not understand how to rotate strictly in global coordinates without using bpy.ops. In reality I will have a large number of objects and do this frequently, so I'd like to use a rotation method of the objects themselves, like one of these: obj.rotate_euler() or obj.matrix_world *= some_vector or obj.rotation_axis_angle()

but I don't understand how to use them for strictly single global axis rotations like the following example:

import bpy
import math

half_pi = 0.5 * math.pi

group = []
for y in [-3, 0, 3]:
    bpy.ops.mesh.primitive_cylinder_add(location=(0, y, 0)) # ops is OK here, but not in the rotations
    obj = bpy.context.active_object
    group.append(obj)

zangles = [1, 1.5, 2] # radains

for obj, zangle in zip(group, zangles):
    bpy.ops.object.select_all(action='DESELECT')
    obj.select = True
    bpy.ops.transform.rotate(value=half_pi, axis=(1, 0, 0))  # rotate about global X by 90 degrees
    bpy.ops.transform.rotate(value=zangle,  axis=(0, 0, 1))  # rotate about global Z by zangle

bpy.ops.object.select_all(action='DESELECT')

ops can rotate globally, but I don't see any methods associated with objects to to global rotation, and I can't figure out how to do that.

A link to a less theoretical, and more "if you want to do this, use this" scripted rotation explanation would also be greatly appreciated. I'm OK with the math, it's the Blender conventions I can not get a handle on.

rotated cylinders

$\endgroup$
  • $\begingroup$ If you are ok with the math, then adjusting the world matrix shouldn't be a problem. There are no blender specific conventions I can think of. $\endgroup$ – Jaroslav Jerryno Novotny Jan 11 '16 at 13:39
  • $\begingroup$ I don't find "world matrix" in the index of my math book. "OK with" means what I don't understand mathematically, I can look up. It does not mean I'm a math genius. I'm trying to explain which kind of tutorial would be the most helpful to me right now. $\endgroup$ – uhoh Jan 11 '16 at 13:49
27
$\begingroup$

TL;DR > Skip to last paragraph

Each object has it's own World Matrix. It's a 4x4 transform matrix that stores the object's final location, rotation and scale. By doing math operations directly on this matrix we can transform the object how ever we want.

Every world matrix can be decomposed into it's components. We will get a

  • location vector (size 3)
  • rotation quaternion (size 4)
  • scale vector (size 3)

    loc, rot, scale = obj.matrix_world.decompose()
    

Now about rotations. A rotation is either represented by a (3x3 or 4x4) Rotation Matrix (Euler or Matrix class in Blender), Quaternion vector (Quaternion class) or an axis (Vector of size 3) with rotation value (radians or degrees). They can be all converted between each other, but we just need the 4x4 rotation matrix. There are different options to get it:

from mathutils import Matrix, Euler, Quaternion

# directly
Matrix.Rotation(angle, 4, axis)
# converting from Euler matrix
Euler((angleX, angleY, angleZ), 'XYZ').to_4x4()
# converting from quaternion
Quaternion((w,x,y,z)).to_matrix().to_4x4()

Transformations are done with matrix multiplication and they can be stacked. The order in which they are multiplied is important. So:

matA * matB * matC

means first we will transform with matC, then matB and last matA (it's reversed!). All the transformations are applied in global space. Here you can see what it looks like to do Translation * Rotation and Rotation * Translation:

enter image description here

The first end-result we can substitute with a local/global translation and then local rotation, and the second we can substitute with a local/global rotation and then local translation.

This means if the matrices are rotation matrices, only the first rotation matrix (and last applied) will result in global space rotation (so matA), because all the others are influenced by it.

This is how a World Matrix is composed. The order is again important:

matrix_world = matLoc * matRot * matScale

So to alter it and add an extra global rotation, we need to sneak a rotation matrix before matRot (so it's applied last and in global space) and after matLoc:

import bpy
from math import radians
from mathutils import Matrix

# we will demonstrate on an active object
obj = bpy.context.active_object

# define the rotation
rot_mat = Matrix.Rotation(radians(angle_in_degrees), 4, 'X')   # you can also use as axis Y,Z or a custom vector like (x,y,z)

# decompose world_matrix's components, and from them assemble 4x4 matrices
orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose()
orig_loc_mat = Matrix.Translation(orig_loc)
orig_rot_mat = orig_rot.to_matrix().to_4x4()
orig_scale_mat = Matrix.Scale(orig_scale[0],4,(1,0,0)) * Matrix.Scale(orig_scale[1],4,(0,1,0)) * Matrix.Scale(orig_scale[2],4,(0,0,1))

# assemble the new matrix
obj.matrix_world = orig_loc_mat * rot_mat * orig_rot_mat * orig_scale_mat 
$\endgroup$
  • $\begingroup$ your explanation is extremely helpful! It will take me some hours to go through it carefully. I am always amazed at the amount of math" under the hood" in Blender. Thank you for taking the time to go into this so deeply! $\endgroup$ – uhoh Jan 12 '16 at 1:32
  • $\begingroup$ ...hmmm and so how do I rotate my cylinder sequentially, first by 90 degrees about global_X and then by 57 degrees (1 radian) around global_Z as asked in the question? I'm learning to appreciate that location, rotation and scale are combined in obj.matrix_world and seeing how you take it a part and put it back together is really very informative. But I still want to know how to do a series of two rotations of an object around global axes without using ops. $\endgroup$ – uhoh Jan 12 '16 at 13:02
  • $\begingroup$ @uhoh sequentially like you want to see the results inbetween or you just want to do this composite rotation of first transforming with globalX and then globalZ (which in matrix math is globalZ * globalX). ? $\endgroup$ – Jaroslav Jerryno Novotny Jan 12 '16 at 13:08
  • $\begingroup$ Yes, just the final result of what those two global rotations would produce if executed sequentially - first about global_X, then about global_Z. $\endgroup$ – uhoh Jan 13 '16 at 4:45
  • 1
    $\begingroup$ thank you for sticking with me here @Jerryno! Yep That works! It's about 6 lines and 14us, which is Great! My starting point (using ops) was two lines, but 400us! So again there is a performance advantage to using the methods associated with the objects themselves, and avoiding using ops. $\endgroup$ – uhoh Jan 14 '16 at 1:37
4
$\begingroup$

One line rotation:

from mathutils import Matrix
import math

obj = bpy.context.active_object
# rotate around global Z-axis
obj.rotation_euler = (Matrix.Rotation(math.pi, 3, 'Z') * obj.rotation_euler.to_matrix()).to_euler()
# or around local axis
obj.rotation_euler = (obj.rotation_euler.to_matrix() * Matrix.Rotation(math.pi, 3, 'Z')).to_euler()

Jerryno already explained the sequence of multiplication.

$\endgroup$
  • $\begingroup$ OK I'll give it a whirl when I get to a keyboard. Thanks! $\endgroup$ – uhoh Mar 1 '17 at 6:45
  • $\begingroup$ Doesn't work in 2.8 $\endgroup$ – hatinacat2000 Aug 30 at 4:44
3
$\begingroup$

Does this do what you need?

import bpy
import math
from mathutils import Matrix

half_pi = 0.5 * math.pi

group = []
for y in [-3, 0, 3]:
    bpy.ops.mesh.primitive_cylinder_add(location=(0, y, 0)) # ops is OK here, but not in the rotations
    obj = bpy.context.active_object
    group.append(obj)

# use the Matrix.Rotation constructor to create a rotation matrix
# half_pi: rotation angle
# 4: matrix size, in this case we will create a 4x4 matrix. 3 and 2 are also valid values for creating 3x3 and 2x2 matrices 
# X: axis about which we want to rotate
hpiMat = Matrix.Rotation(half_pi, 4, 'X')

print (hpiMat)

# do the same again for the individual zangle rotations
aMat = Matrix.Rotation(1, 4, 'Z')
bMat = Matrix.Rotation(1.5, 4, 'Z')
cMat = Matrix.Rotation(2, 4, 'Z')

print (aMat)
print (bMat)
print (cMat)

zangles = [aMat, bMat, cMat] # list of zangle rotation matrices

for obj, zangle in zip(group, zangles):

    # construct the final rotation matrix for the object by multiplying the half pi matrix with the current zangle matrix
    finalMat = zangle * hpiMat

    # mulitply the final rotation matrix against the object's world matrix
    obj.matrix_world = obj.matrix_world * finalMat

also gives the output:

<Matrix 4x4 (1.0000, 0.0000,  0.0000, 0.0000)
            (0.0000, 0.0000, -1.0000, 0.0000)
            (0.0000, 1.0000,  0.0000, 0.0000)
            (0.0000, 0.0000,  0.0000, 1.0000)>
<Matrix 4x4 (0.5403, -0.8415, 0.0000, 0.0000)
            (0.8415,  0.5403, 0.0000, 0.0000)
            (0.0000,  0.0000, 1.0000, 0.0000)
            (0.0000,  0.0000, 0.0000, 1.0000)>
<Matrix 4x4 (0.0707, -0.9975, 0.0000, 0.0000)
            (0.9975,  0.0707, 0.0000, 0.0000)
            (0.0000,  0.0000, 1.0000, 0.0000)
            (0.0000,  0.0000, 0.0000, 1.0000)>
<Matrix 4x4 (-0.4161, -0.9093, 0.0000, 0.0000)
            ( 0.9093, -0.4161, 0.0000, 0.0000)
            ( 0.0000,  0.0000, 1.0000, 0.0000)
            ( 0.0000,  0.0000, 0.0000, 1.0000)>
$\endgroup$
  • $\begingroup$ Excellent! Yes indeed it does. So in Matrix.Rotation(1, 4, 'Z') does the 'Z' always refer to global coordinates? No matter what else I might do? $\endgroup$ – uhoh Jan 11 '16 at 14:11
  • 1
    $\begingroup$ From what I can see, 'Z' doesn't always refer to global Z. E.g. if you rotate your object about the X axis in the 3d viewport, and then using the python console, you multiply the matrix_world with a Z rotation matrix like above, you'll see that the object isn't rotated about global Z. One way around this is to apply the first rotation before rotating in python. To apply rotation, first set the active object and then use: bpy.ops.object.transform_apply(location=False, rotation=True, scale=False). Now try the Z rotation again, and the object will be roatated about global Z. Does that make sense? $\endgroup$ – fergal Jan 11 '16 at 14:28
  • 2
    $\begingroup$ @uhoh the 'Z' does mean global Z axis in the context of matrix. You can also use any 3D vector instead as a rotation axis. What matters is the rotation order. If you rotate around Z and then around X, the Z rotation will be global. Vice-versa it will be local. The order you multiply the matrices defines the rotation order. $\endgroup$ – Jaroslav Jerryno Novotny Jan 11 '16 at 14:59
  • 1
    $\begingroup$ @uhoh Yes you are right, the script above works only in this case. It is clearly defined if you google Rotation Matrix on wiki and also see what a World Matrix is made of. I might write a bit math answer in about an hour (can't now) to explain how exacly does this work and appropriate blender commands to do the math operations. $\endgroup$ – Jaroslav Jerryno Novotny Jan 11 '16 at 15:20
  • 2
    $\begingroup$ @uhoh for example this: en.wikipedia.org/wiki/Rotation_matrix. I'll make an blender specific answer, it's not well documented here. $\endgroup$ – Jaroslav Jerryno Novotny Jan 11 '16 at 15:32
0
$\begingroup$

You can also parent the object in an empty set it's rotation there and let the system computer the exact same thing through the parent chain but you also get animation etc. as a bonus.

$\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.