Rotating Around An Arbitrary Axis

(scorpius) #1

I want to use python to rotate an object around an arbitrary axis. I’ve read The Matrix and Quaternions FAQ, but I need more help. This is how to do it with POV-Ray:

#local VX = vnormalize(V); // assuming that V is the axis of rotation
#local VY = vnormalize(vcross(VX,<VX.y,VX.z,-VX.x>));
#local VZ = vnormalize(vcross(VX,VY));

object { MyObject
  matrix < VX.x,VY.x,VZ.x,
              0,   0,   0 >

  rotate x*Angle // here Angle is your angle of rotation

  matrix < VX.x,VX.y,VX.z,
              0,   0,   0 >

To do it with Blender, I need 2 functions: matrix2euler() and euler2matrix() because Blender will not let you directly assign a transformation matrix to an object.

Does anyone have a python function that returns the final rotation matrix for an object rotated around an arbitrary axis? Eeshlo did some work in this area and I hope he has some suggestions.

(theeth) #2

that’s from eeshlo:

# creates full 3D rotation matrix
# rx, ry, rz angles in radians
def makeRotMtx3D(rx, ry, rz):
	A, B = sin(rx), cos(rx)
	C, D = sin(ry), cos(ry)
	E, F = sin(rz), cos(rz)
	AC, BC = A*C, B*C
	return [[D*F, 	   D*E, 	 -C],
			[AC*F-B*E, AC*E+B*F, A*D],
			[BC*F+A*E, BC*E-A*F, B*D]]

You can probably make the inverse function by reverse engineering it.


(eeshlo) #3

You can find the mat2euler and other functions here:

The Lightflow export script makes use of this too, it contains a function to get all information from a Blender matrix, rotation in euler angles, scaling & translation.

(jms) #4

does it work if object is parented?


(eeshlo) #5

Yes it does, that is why I needed to create it in the first place, you can’t use matrices directly in Lightflow, not in Python anyway.

Unless you mean getting the actual orientation of the object without the parent transformation included. To do that you would have to multiply the inverse matrix of the parent with the matrix of the child to ‘undo’ the parent transformation.

(Hos) #6

I wrote a quaternion class a long time
ago that supported rotations about
arbitrary axes – maybe there is some
code that can be pinched:


(jms) #7


Yes it does, that is why I needed to create it in the first place, you can’t use matrices directly in Lightflow, not in Python anyway.

Unless you mean getting the actual orientation of the object without the parent transformation included. To do that you would have to multiply the inverse matrix of the parent with the matrix of the child to ‘undo’ the parent transformation.[/quote]

Thank for this answer. Could we apply the solution to this


(aayers) #8

matrix 2 euler:
Keep in mind that this gives you euler angles in the YPR (z-rot, y-rot, x-rot) sequence,

def rmatrix2euler(M):
                #M is the Rotation matrix
                if(M[2][0] == 1.0):

                        Z = atan2(-M[0][1],-M[0][2])
                        Y = -pi/2
                        X = 0.0
                elif(M[2][0] == -1.0):
                        Z = atan2(M[1][2],M[0][2])
                        Y = pi/2
                        X = 0.0
                        Z = atan2(M[1][0],M[0][0])
                        Y = asin(-M[2][0])
                        X = atan2(M[2][1],M[2][2])

                return array([[Z],[Y],[X]])
                #YPR sequence

If feel happy that I keep these things in my log. :wink:

Cheers, Alex

(eeshlo) #9

Yes, you can, but not with the function you used, that did have some problems, I posted some improvements later into those posts. But here is your example code with what I use now in the Lightflow script:

# normalize vector inplace, return length
def vnormlen(v):
	vlen = sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
	if vlen!=0.0:
		d = 1.0/vlen
		v[0]*=d;  v[1]*=d;  v[2]*=d
	return vlen

# extract euler rotation, scale & position from a single matrix, necessary for parent/child transformations
def infoFromMtx(mat):
	mtx = [list(mat[0][:3]), list(mat[1][:3]), list(mat[2][:3])]
	scale = [0.0, 0.0, 0.0]
	scale[0] = vnormlen(mtx[0])
	scale[1] = vnormlen(mtx[1])
	scale[2] = vnormlen(mtx[2])
	angle_y = -asin(max(min(mtx[0][2], 1.0), -1.0))
	C = cos(angle_y)
	if C!=0.0: C = 1.0/C
	angle_x = atan2(mtx[1][2] * C, mtx[2][2] * C)
	angle_z = atan2(mtx[0][1] * C, mtx[0][0] * C)
	return (angle_x, angle_y, angle_z), tuple(scale), tuple(mat[3][:3])

import Blender
from Blender import Object
from math import *

O1 = Object.Get('Plane')
O2 = Object.Get('Plane.001')
O2.rot, O2.size, loc = infoFromMtx(O1.mat)

This will orient the other object correctly. The function also returns location, but that is not used here.

(jms) #10


a new problem with this code that works great the first time I used, but after a new parenting…:


(eeshlo) #11

:frowning: … Now I have to rewrite my code…
I have been trying to figure this out all day, and I found two problems: First, I completely forgot about negative scaling, but that was relatively easy to solve. Second, non-uniform scaling by parent objects doesn’t work, and I don’t yet know what to do about that. In fact, it actually seems really impossible to solve from what I understand. Non-uniform scaling on non-parented objects does work as it should though.
I have to come up with something completely different… So this means more delay for me, again…
Thanks anyway JM for finding the errors… :-?

(jms) #12

Povanim has the same problem
(Michel Maigrot showed me the
error last month, and Hos wrote a
comment about this problem in the
blenderize function of his parents
module) but I can export the matrix
to povray when sizing is “annoying”.

Evidently, I’m interested in a better
solution. I talked about with a french
university mathematics teacher
which works on it …I hope.


(Hos) #13

Just a real quick note:

I think the main problem is that
there is more than one way to
do the same thing. As an example,
take a 2D case: if you flip (scale = -1)
an object in both the x and y
direction, the net effect is
rotating the object 180 degrees.
Thus these two different operations
will have the same transformation

I think the best you can do is take
the determinant of the transformation
matrix. If this determinant is less
than zero, take one of the scalings
to be negative (pick your favorite)
and modify your transformation matrix
accordingly, i.e., negate that row
that corresponds to that scale value)
then calculate the rest of the angles,


(eeshlo) #14

That is exactly how I solved the negative scale problem. The problem here is non-uniform scaling of the parent. Uniform scaling (negative or positive) works without problems, orientation is correct also. But as soon as you scale the parent in only one axis for instance, everything goes pearshaped (in this case almost litterally, english expression apparently) and the scaling and rotation is not recoverable using this method, or any other that I know of, which of course does not mean that there isn’t any. I vaguely remember having read something somewhere that it is at least possible to remove the non-uniform scaling, but I’m not sure.
Of course since it is possible to follow the hierarchy with object.parent, it is possible to keep track of everything yourself. But this is a rather complicated (and slow) method. But that is the only solution that I can think of for now. All these quirks of Lightflow really have slowed things done for me.

(Hos) #15

Yes, the child becomes under the influence of a shear
transformation (involving nonorthogonal eigenvectors),
which isn’t solvable in terms of simple Loc, Rot, Scale alone
(which will only yield orthogonal eigenvectors). Following the
heirarchy is the only way to go.


(eeshlo) #16

Following the hierarchy is maybe not the solution. Just going along the hierarchy and collecting rotation & scaling does not solve the problem that the transformations are relative to the initial state of the parent object, and there is no way to know what that initial transform is. Unless somebody can tell me otherwise, I’m no expert in this field… The shearing caused by non-uniform scaling would still exist too afaik…
I did find something else that also deals with the matrix problem, it’s called ‘polar decomposition’ by Ken Shoemake, sourcecode for it is in Graphics Gems IV. I tested this and it does seem to fit the rotations better, but the shearing factor still prevents a perfect fit.
I really can’t see any other solution but to use the matrix directly, unfortunately there doesn’t seem to be any way to do that with the Python/Lightflow combination. So basically, the script in it’s current form is not going to work well, unless I use the matrix directly to transform the meshes before exporting, but that makes it not very flexible…

(theeth) #17

well, people should use in-edit mode scaling for anything that touches modelling, anyway. And non-uniform scaling often cause some bugs in Blender (scaling to 0 in one direction is fatal to translation).


(Hos) #18

How do you specify transformations in Lightflow?
A matrix? Or is it with Loc, Rot, Scale only?
Does it support parenting?


(eeshlo) #19

It is all done with indirect matrix manipulation. Only rotation, scaling, translation are supported, as well as a number of other undocumented functions as ReflectionOnXY, AlignmentTo, rotationAroundAxis etc… No direct matrix write access, it is read-only, at least I can’t find any way to set it directly, I have tried, I would have no problems if it did. But I am going to forget about it, I am just going to adapt the documentation for the script to warn users that they should only use uniform scaling on parents or otherwise scale in edit mode. I had lots of plans for things I was going to do (partly started already), but it is all taking too long, I can’t let theeth wait any longer before I get back to work on Dynamica…

(beatabix) #20

rather than saying “do it in edit mode”, couldn’t you tell people to apply scale before they export (i forget the hotkey --> is it CTRL+A?).

it might be a more elegant solution.