18
$\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$
2
  • $\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$ Commented Jan 11, 2016 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
    Commented Jan 11, 2016 at 13:49

6 Answers 6

46
$\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:

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

We get a

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

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). They can be all converted between each other, but we just need the 4x4 rotation matrix. There are different options to construct 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 the order in which they are multiplied is important, as the operation is not commutative. Doing:

matA @ matB @ matC

means the result will be like first transforming with matC, then matB and last matA (it's reversed from how you multiply them). All the transformations are applied in global space.

Here you can see what it looks like to do Translation @ Rotation (on the left) and Rotation @ Translation (on the right):

enter image description here

The first end-result we can substitute with a local translation and then local rotation, and the second we can substitute with a local rotation and then local translation. This means only the first matrix (matA) will represent a global transformation in the end, because all the others were influenced by it.

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

matrix_world = matLoc @ matRot @ matScale

To alter it and to add an extra global rotation, we need to sneak our rotation matrix between matRot and matLoc (so it's applied as last rotation = in global space):

new_matrix_world = origLoc @ matRot @ origRot @ origScale

Here's an example code:

import bpy
from math import radians
from mathutils import Matrix

# example on an active object
obj = bpy.context.active_object

# define some rotation
angle_in_degrees = 45
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$
9
  • $\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
    Commented Jan 12, 2016 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
    Commented Jan 12, 2016 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$ Commented Jan 12, 2016 at 13:08
  • 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
    Commented Jan 14, 2016 at 1:37
  • 2
    $\begingroup$ use obj.matrix_world = orig_loc_mat @ rot_mat @ orig_rot_mat @ orig_scale_mat for 2.8 and above $\endgroup$
    – simone
    Commented Aug 10, 2021 at 6:43
7
$\begingroup$

Refining Ruslan's solution using 2.8 syntax:

def rotate_object(obj, angle_degrees, axis='Z'):
    ''' rotates an object '''
    from math import radians
    from mathutils import Matrix

    # local rotation about axis
    obj.rotation_euler = (obj.rotation_euler.to_matrix() @ Matrix.Rotation(radians(angle_degrees), 3, axis)).to_euler()
$\endgroup$
0
6
$\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$
3
  • $\begingroup$ OK I'll give it a whirl when I get to a keyboard. Thanks! $\endgroup$
    – uhoh
    Commented Mar 1, 2017 at 6:45
  • 1
    $\begingroup$ Doesn't work in 2.8 $\endgroup$ Commented Aug 30, 2019 at 4:44
  • 1
    $\begingroup$ @hatinacat2000 this has now been addressed here $\endgroup$
    – uhoh
    Commented Jul 28, 2020 at 3:31
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$
9
  • $\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
    Commented Jan 11, 2016 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
    Commented Jan 11, 2016 at 14:28
  • 3
    $\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$ Commented Jan 11, 2016 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$ Commented Jan 11, 2016 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$ Commented Jan 11, 2016 at 15:32
1
$\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$
1
$\begingroup$

Expanding on Jaroslav Jerryno Novotny's answer,
Incase anyone wants to change the global rotation TO a certain orientation and not BY

( what's the difference? in by you "add" to your existing rotation, in to you directly "=" the new rotation )

import bpy,math
from mathutils import Matrix, Vector


def global_rotation_matrix(x_deg=0,y_deg=0,z_deg=0,order="XYZ",size=4): # matrix= size x size
    
    # creating unit vector based on size
    unit_matrix=[]
    for i in range(size):
        axis_vector=[]
        for j in range(size):
            if(i==j):
                axis_vector.append(1)
            else:
                axis_vector.append(0)
        unit_matrix.append(axis_vector)
    
    # creating new global rotation matrix
    new_matrix=[]
    for vec in unit_matrix:
        vec=Vector(vec)
        for axis in reversed(order):
            if(axis=="X" or axis=="x"):
                vec=vec @ Matrix.Rotation(math.radians(x_deg),size, 'X')
            elif(axis=="Y" or axis=="y"):
                vec=vec @ Matrix.Rotation(math.radians(y_deg), size, 'Y')
            elif(axis=="Z" or axis=="z"):
                vec=vec @ Matrix.Rotation(math.radians(z_deg), size, 'Z')
        new_matrix.append(vec)
        
    return Matrix(new_matrix)

def global_rotate_obj(obj,x_deg=0,y_deg=0,z_deg=0,order="XYZ"):
    orig_loc, orig_rot, orig_scale = obj.matrix_world.decompose()
    orig_loc_mat = Matrix.Translation(orig_loc)
    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))
    
    obj.matrix_world = orig_loc_mat @ global_rotation_matrix(x_deg,y_deg,z_deg,order) @ orig_scale_mat


obj=bpy.data.objects["Cube"]
global_rotate_obj(obj,10,0,0)

or using EMCS's solution & simplifying further:

import bpy,math
from mathutils import Matrix, Vector

def global_rotation_matrix(x_deg=0,y_deg=0,z_deg=0,order="XYZ",size=4): # matrix= size x size

    # creating unit vector based on size
    unit_matrix=[ [1 if i==j else 0 for j in range(size)] for i in range(size) ]

    # creating new global rotation matrix
    new_matrix=[]
    for vec in unit_matrix:
        vec=Vector(vec)
        for axis in reversed(order):
            if(axis=="X" or axis=="x"):
                vec=vec @ Matrix.Rotation(math.radians(x_deg),size, 'X')
            elif(axis=="Y" or axis=="y"):
                vec=vec @ Matrix.Rotation(math.radians(y_deg), size, 'Y')
            elif(axis=="Z" or axis=="z"):
                vec=vec @ Matrix.Rotation(math.radians(z_deg), size, 'Z')
        new_matrix.append(vec)

    return Matrix(new_matrix)

obj=bpy.data.objects["Cube"]
obj.rotation_euler = global_rotation_matrix(30,0,10).to_euler()
$\endgroup$
1
  • 1
    $\begingroup$ Excellent, thank you! $\endgroup$
    – uhoh
    Commented Nov 21, 2022 at 8:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .