Local tranforms

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 :slight_smile:

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 :slight_smile:

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 :slight_smile:

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