22
$\begingroup$

How do I convert from object-space to world-space?

I suspect it is:

vert_os = obj.vertices[foo]
vert_ws = vert_os.getPositionFromMatrix(obj.matrixWorld)

but does the matrix also handle translations, or do I need to handle that separately?

Can someone link to the relevant documentation?

$\endgroup$

6 Answers 6

49
$\begingroup$

Multiply the world matrix by the object-space vector for world space coordinate:

import bpy

ob = bpy.data.objects['Cube']

v = ob.data.vertices[0].co
mat = ob.matrix_world

# Multiply matrix by vertex (see also: https://developer.blender.org/T56276)
loc = mat @ v

# Don't do the reverse!
loc = v @ mat  # wrong!

`Object.matrix_world is a 4x4 transformation matrix. It contains translation, rotation and scale. A 4x4 matrix can also be used for mirroring and shearing (not covered in my answer).

[a] [b] [c] [d]
[e] [f] [g] [h]
[i] [j] [k] [l]
[m] [n] [o] [p]

The translation is stored in the first 3 rows of the 4th column of the matrix (d, h, l):

mat.col[3][:3]

You can also use:

# Create new Vector object
mat.to_translation()

# Access the original matrix' translation
# (assignments will change the matrix, thus the object location!)
mat.translation

Rotation and scale are sort of combined. The rotation is stored in a, b, c, e, f, g, i, j and k. The scale is stored in a, f and k. The values in a, f and k of the rotation are taken and multiplied by the scale factor to store both pieces of information.

To get only the rotation, you need to normalize the 3x3 matrix:

mat.to_3x3().normalized()

To get only the scale, you can use the utility method:

mat.to_scale()

Or manually, normalize the matrix and divide each of the un-normalized by the normalized components (a, f, k):

# you could do this manually like
# vec / sqrt(vec.x**2 + vec.y**2 + vec.z**2) for every matrix column
nmat = mat.normalized()

scale = Vector((mat[0][0] / nmat[0][0], mat[1][1] / nmat[1][1], mat[2][2] / nmat[2][2]))

If you need the world coordinates of all vertices, it's more efficient to use the transform() method:

me.transform(mat)

It will apply the transformation matrix to the mesh, so multiply the world matrix with all vertex coordinates. You may wonder about the change in orientation of a mesh object in viewport if you do the above. It can be fixed by resetting the matrix_world (otherwise the transformation will be done twice):

ob.matrix_world = Matrix()  # identity matrix
$\endgroup$
2
  • $\begingroup$ Maybe you mean v = ob.data.vertices[0].co instead of v = ob.data.vertices[0]. $\endgroup$
    – isar
    Commented Jul 5, 2015 at 19:25
  • 1
    $\begingroup$ Love how "translation stored in d, h, l" — could be a tagline for DHL :D $\endgroup$ Commented Feb 5, 2021 at 15:48
19
$\begingroup$

This is fairly simple and applies to any data (curves, armatures, lattice ...)

v_co_world = obj.matrix_world @ obj.data.vertices[0].co

Its also handy to be able to do the reverse, get a point in worldspace relative to the vertex.

# get the cursor in object space
# (so you can compare it to the vertices locations
#  without first having to transform them into worldspace).
v_co_object = obj.matrix_world.inverted() @ scene.cursor_location
$\endgroup$
1
  • $\begingroup$ For me, the camera's matrix_world was an identity matrix, so to get the coordinates of a point relative to the camera, I needed camera.matrix_basis.inverted() * mathutils.Vector([0,0,0]) $\endgroup$
    – colllin
    Commented Apr 12, 2019 at 22:06
8
$\begingroup$

In blender 2.8 use @ operator for matrix multiplication

for example

transformed_vertex =obj.matrix_world @ obj.data.vertices[0].co
$\endgroup$
2
  • $\begingroup$ I'm using Blender 2.8--this worked for me without importing NumPy. Using * failed. $\endgroup$ Commented May 23, 2019 at 22:27
  • 2
    $\begingroup$ @ is a operator in python. For more info PEP465 $\endgroup$ Commented May 30, 2019 at 8:26
6
$\begingroup$

If you're brave enough to use numpy you can get all the vertex coords as a numpy array about a thousand times as fast as any other python method:

import numpy as np


    def get_co(ob, arr=None):
    """Returns vertex coords as N x 3"""
    c = len(ob.data.vertices)
    if arr is None:    
        arr = np.zeros(c * 3, dtype=np.float32)
    ob.data.vertices.foreach_get('co', arr.ravel())
    arr.shape = (c, 3)
    return arr


def get_proxy_co(ob, arr):
    """Returns vertex coords with modifier effects as N x 3"""
    me = ob.to_mesh(bpy.context.scene, True, 'PREVIEW')
    c = len(me.vertices)
    me.vertices.foreach_get('co', arr.ravel())
    bpy.data.meshes.remove(me)
    arr.shape = (c, 3)
    return arr


def apply_transforms(ob, co):
    """Get vert coords in world space"""
    m = np.array(ob.matrix_world)    
    mat = m[:3, :3].T # rotates backwards without T
    loc = m[:3, 3]
    return co @ mat + loc


def revert_transforms(ob, co):
    """Set world coords on object. 
    Run before setting coords to deal with object transforms
    if using apply_transforms()"""
    m = np.linalg.inv(ob.matrix_world)    
    mat = m[:3, :3].T # rotates backwards without T
    loc = m[:3, 3]
    return co @ mat + loc
$\endgroup$
5
  • $\begingroup$ A thousand times faster? $\endgroup$
    – batFINGER
    Commented Sep 20, 2017 at 5:52
  • 2
    $\begingroup$ I just did a speed check. against loop and list comprehension. on an object with 60,000 verts, numpy was only 20 times faster. Sorry about the exaggeration. $\endgroup$ Commented Sep 20, 2017 at 17:03
  • $\begingroup$ 20 is still good. Care to add test result to answer? $\endgroup$
    – batFINGER
    Commented Sep 20, 2017 at 17:06
  • $\begingroup$ I'm not good at posting here. Is there a way I can send you my code? $\endgroup$ Commented Sep 20, 2017 at 17:09
  • 1
    $\begingroup$ I posted the code on git link $\endgroup$ Commented Sep 21, 2017 at 17:48
4
$\begingroup$

If you have your coords in a flat numpy.array from foreach_get("co", coords), the following dot product of the world matrix with the local coordinates will get their their global version:

import numpy as np

def to_global(local_coords, world_matrix):

    # Reshape coords to Nx3 matrix
    local_coords.shape = (-1, 3)

    # Add an extra 1.0s column (for matrix dot product)
    local_coords = np.c_[local_coords, np.ones(local_coords.shape[0])]

    # Then:
    # Dot product matrix with the coords transpose
    # Keep the first 3 rows (x,y,z)
    # Transpose result to Nx3
    # Flatten
    global_coords = np.dot(world_matrix, local_coords.T)[0:3].T.reshape((-1))

    return global_coords

To use:

# Get the local coords
local_coords = np.zeros(len(my_obj.data.vertices) * 3) 
my_obj.data.vertices.foreach_get('co', local_coords)

# Convert to global
global_coords = to_global(local_coords, my_obj.matrix_world)

On my machine and a mesh with 491,526 verts, the function takes 8.1 ms, or about 0.16ms per 10k verts:

>>> timeit.timeit(lambda: to_global(local_coords, my_obj.matrix_world), number=1000) / 1000.0
0.008153792893048377
$\endgroup$
1
$\begingroup$
import bpy

obj = bpy.data.objects['Cube']

wm = obj.matrix_world
print( wm )
for v in obj.data.vertices:
   world = wm * v.co
   print(world)

The world matrix contains the transformations for location,rotation and scale as in the Properties Panel:

enter image description here

The relevant documentation on Math Types & Utilities mathutils

$\endgroup$

You must log in to answer this question.

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