When I use bpy.ops.transform.translate or bpy.ops.transform.rotate the movement is always global. What is the bpy method for moving things in a local direction?
Maybe I should give a more specific example?
The following script can be pasted into the script editor, so that when run, the selected object (e.g. the default cube) moves. This is a simple “transform” function which works with local axis rotation, but unfortunately, is using global axis for translational movement. I would like the translation to move the object in a local direction. My maths (degrees / radians etc) is non-existent, though I suspect math.sin() and math.cos() might come into play, unless there is a pre-built-in blender alternative.
Go down to the last lines of the script (the text drive area) to try out different values or to make a series of movements, old school logo-bot style.
import bpy, time, math
def longest(a, b, c):
# return the longest of three lengths.
# Note that this nullifies negative distances to positive
result = abs(a)
if abs(b) > result:
result = abs(b)
if abs(c) > result:
result = abs(c)
return result
def transform(locx, locy, locz, rotx, roty, rotz):
# move selected object from old (position A) to new (position B) location and rotation.
# Note that at this time, rotation is local direction whereas translation is global
# The total number of steps form angle position A to position B
# Will later use the longest(a, b, c) function to determine the largest movement and use that as the number of steps
jumpsteps = 25
# ROTATION ###############################
# rotational variables
degree = math.pi/180
# Get current rotation
nowrotx = bpy.context.object.rotation_euler[0] / degree
nowroty = bpy.context.object.rotation_euler[1] / degree
nowrotz = bpy.context.object.rotation_euler[2] / degree
# Find out how much each axis needs to rotate every turn
rotstepx = rotx / jumpsteps
rotstepy = roty / jumpsteps
rotstepz = rotz / jumpsteps
# TRANSLATION #############################
# Get current location
nowlocx = bpy.context.object.location[0]
nowlocy = bpy.context.object.location[1]
nowlocz = bpy.context.object.location[2]
# Find out how much each axis needs to translate each step
locstepx = locx / jumpsteps
locstepy = locy / jumpsteps
locstepz = locz / jumpsteps
# Perform the translation ###################################
for turn in range(0, jumpsteps):
# perform rotation
nowrotx += rotstepx
nowroty += rotstepy
nowrotz += rotstepz
# perform translation
nowlocx += locstepx
nowlocy += locstepy
nowlocz += locstepz
bpy.context.object.rotation_euler = (degree * nowrotx, degree * nowroty, degree * nowrotz) # rotate
bpy.context.object.location = (nowlocx, nowlocy, nowlocz) # translate
# refresh window to show the action
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
return 0
### Test Drive begin
# Place travel directions in here
transform(0, -1, 0, 45, 0, 0) # e.g. move the selected object global -1 along Y axis while rotating 45 around local X
I suggest you check out the object.matrix_local.
It’s a 4x4 matrix, for translation you want to use the first 3 values of the last column (for x, y, z)
Thanks for the pointer richardvdoost. I’ve been reading up at khanacademy.org to find out how matrices work… still going through the basics. Is there any documentation which explains how blender uses the object.matrix_local procedure and what the elements of the 4x4 matrix represent?
So far my googling is coming up with a lot of information about armatures and such, which I think are off topic from what I am trying to do.
Edit:
Okay - I don’t know what I’m doing here, but playing around with the object.matrix_local matrix, I’ve observed the following.
Scale and location of an object are affected by these parts of the matrix…
[sc_x][ ][ ][locx]
[ ][sc_y][ ][locy]
[ ][ ][sc_z][locz]
[ ][ ][ ][ ]
Again, location made by manipulating the rightmost column of the matrix is in a global direction, not local. If the object is rotated 45 degrees, the movement is still made in a global direction.
Rotation is all over the place. It appears that rotation of the X, Y, Z directions affect these respecive parts of the matrix…
[-yz][--z][-y-][---]
[--z][x-z][x--][---]
[-y-][x--][xy-][---]
[---][---][---][---]
There are some “unused” parts. Changing them results in the object taking on a warped appearance, particularly the bottom row, which warps the abject along x, y, z and then [xyz] respectively.
With the "warping, the actual local azis arrows end up pointing in odd directions.
Anyone know where I can go to understand how local movement works? How is movement along the Y ([forward] axis) calculated to in order to derive the final destination coordinates in terms of where the object ends up in global axis terms?
yeah you’re right, the first 3x3 sub-matrix is the rotation matrix, the bottom row isn’t really used as far as I know.
But you don’t have to bother with that, if you just want to translate in local space.
For example, you want to translate 2.0 units in local Y space. Just add 2.0 to the matrix_local - loc_y: object.matrix_local[1][3] += 2.0
By the way, khanacademy.org is awesome. I learned most of my linear algebra from there
Thanks, but here’s the thing… I’ve been trying that but it only moved the object globally (ignoring it’s initial rotation)
Unless there’s something wrong with my use of “context”? Here’s the command I was testing for a selected object which had been rotated 45 degrees around Z axis…
bpy.context.object.matrix_local[1][3] += 1
That’s because blender applies translations before rotations, so first it translates locally in Y (which is the same as the global Y if you object is not parented to something), then performs the rotation.
What you want to do involves a little linear algebra. Here’s a quick solution:
import bpy
from mathutils import Vector, Matrix
# Our test object
object = bpy.context.scene.objects['Cube']
# Define the translation we want to perform in local space (after rotation)
trans_local = Vector((0.0, 1.0, 0.0))
# Convert the local translation to global with the 3x3 rotation matrix of our object
trans_world = object.matrix_world.to_3x3() * trans_local
# Apply the translation
object.matrix_world.translation += trans_world
I added an overload of comments so you know whats going on. I’m basically converting your local translation to a global translation.
Hope this helps
Haha - that’s awesome, thanks. And thank you for the detail in the comments. I still don’t know exactly why it works (I’ll need to find out about mathutils.Vector and object.matrix_world.to_3x3) but it does. I think I have enough now to make what I want to work, happen.
Essentially, the (non-human friendly) code for local Y movement comes down to…
import bpy, mathutils
trans_world = bpy.context.object.matrix_world.to_3x3() * mathutils.Vector((0.0, 1.0, 0.0))
bpy.context.object.matrix_world.translation += trans_world
blender.org and many of my searches aren’t working at the moment (the links from google time out - internet sluggish) but I’ve so far uncovered some interesting information here: http://blender.stackexchange.com/questions/6155/how-to-convert-coordinates-from-vertex-to-world-space.
Thanks very much for your answers.
Current code for the translate (could well be simplified):
import bpy, time, math, mathutils
def longest(a, b, c):
# return the longest of three lengths.
# Note that this nullifies negative distances to positive
result = abs(a)
if abs(b) > result:
result = abs(b)
if abs(c) > result:
result = abs(c)
return result
def transform(locx, locy, locz, rotx, roty, rotz):
# move selected object from old (position A) to new (position B) location and rotation.
# Note that the movement is in a straight linek, according to local directions taken from first frame.
# The total number of steps form angle position A to position B
# Will later use the longest(a, b, c) function to determine the largest movement and use that as the number of steps
jumpsteps = 25
# rotational variables
degree = math.pi/180
##### Get current properties
# Get current rotation
nowrotx = bpy.context.object.rotation_euler[0] / degree
nowroty = bpy.context.object.rotation_euler[1] / degree
nowrotz = bpy.context.object.rotation_euler[2] / degree
# Get current location
nowlocx = bpy.context.object.location[0]
nowlocy = bpy.context.object.location[1]
nowlocz = bpy.context.object.location[2]
# Work out the final destination
# final location
destination_loc = bpy.context.object.matrix_world.to_3x3() * mathutils.Vector((locx, locy, locz))
destlocx = destination_loc[0]
destlocy = destination_loc[1]
destlocz = destination_loc[2]
#### Work out the transform
# Find out how much each axis needs to rotate every turn
rotstepx = rotx / jumpsteps
rotstepy = roty / jumpsteps
rotstepz = rotz / jumpsteps
# Find out how much each axis needs to translate each step
locstepx = destlocx / jumpsteps
locstepy = destlocy / jumpsteps
locstepz = destlocz / jumpsteps
# Perform the translation ###################################
for turn in range(0, jumpsteps):
# perform rotation
nowrotx += rotstepx
nowroty += rotstepy
nowrotz += rotstepz
# perform translation
nowlocx += locstepx
nowlocy += locstepy
nowlocz += locstepz
bpy.context.object.rotation_euler = (degree * nowrotx, degree * nowroty, degree * nowrotz) # rotate
bpy.context.object.location = (nowlocx, nowlocy, nowlocz) # translate
# refresh window to show the action
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
return 0
### Test Drive begin
# Place travel directions in here
for loop in range(0, 8):
transform(0, 8, 0, 0, 0, -45) # e.g. move the selected object global 8 along initial Y axis (local starting direction) while spinning 90 around local Z
Yep, looks good! Glad to help
For Blender 2.8 and on use ‘@’ rather than ‘*’
destination_loc = bpy.context.object.matrix_world.to_3x3() * mathutils.Vector((locx, locy, locz))
destination_loc = bpy.context.object.matrix_world.to_3x3() @ mathutils.Vector((locx, locy, locz))
To make this a concrete example using the default cube, add this after the imports:
cs=bpy.context.scene #current scene
co=cs.objects[‘Cube’]
bpy.context.active_object
Okay, I made those functions and they should behave as unity3D Transform.Translate and Transform.Rotate
from mathutils import Matrix
def local_translate(obj, direction):
direction = direction.copy()
direction.rotate(obj.matrix_world)
obj.location += direction
def local_rotate(obj, rotation):
loc, rot, scale = obj.matrix_world.decompose()
rotation = rotation.copy()
rotation.rotate(rot)
if not isinstance(rotation, Matrix):
obj.matrix_world = rotation.to_matrix().to_4x4()
else:
obj.matrix_world = rotation.to_4x4()
obj.location = loc
obj.scale = scale