# What is the fastest way to set global vertices coordinates to a numpy array using bpy?

The fastest way I know to get the vertices coordinates from the mesh is to use .foreach_get() method:

import bpy
import numpy as np

def vertices_co_to_numpy():
ob = bpy.context.object
vertices = ob.data.vertices
target = np.zeros([len(vertices)*3], dtype="f")

vertices.foreach_get("co", target)
print(target)

vertices_co_to_numpy()


It works extremely fast, but returns vertices in the local coordinates. The way to get vertex global coordinates is to multiply object matrix world by these coordinates:

import bpy
from bpy.types import Object, MeshVertex

def vertex_global_co(obj: Object, vertex: MeshVertex):
print(obj.matrix_world @ vertex.co)

vertex_global_co(bpy.context.object, bpy.context.object.data.vertices[0])


So the most straightforward way to get global coordinates is to multiply object matrix world by each vertex coordinates and assign them to numpy array in a for loop, but it is obviously too slow. Before I proceed I have to warn you that I am really not good in linear algebra, so I have only a very rough idea of what I am trying to perform below. I've tried to make numpy.matrix from the object matrix world, but it could not be multiplied by numpy array made of vertices coordinates because object matrix world is 4D and the vertices coordinates are 3D. So I've tried to convert object matrix world to 3x3 matrix first and it even works, but returns a matrix instead of converted array and the values are different:

import bpy
import numpy as np

def vertices_global_co():
obj = bpy.context.object
verts = obj.data.vertices
mw = obj.matrix_world.to_3x3()
obj_matrix = np.matrix(mw)

# Store vertices to 1D numpy array
verts_co_1D = np.zeros([len(verts)*3], dtype='f')
verts.foreach_get("co", verts_co_1D)

# Convert 1D array to array of 3D arrays
verts_co_3D = np.zeros([len(verts), 3], dtype='f')
verts_co_3D[:, 0] = verts_co_1D[0::3]
verts_co_3D[:, 1] = verts_co_1D[1::3]
verts_co_3D[:, 2] = verts_co_1D[2::3]

reference_matrix = np.array([obj.matrix_world @ v.co for v in verts])
result_matrix = verts_co_3D[:] @ obj_matrix

print(f"Reference: {reference_matrix}\n")
print(f"Result: {result_matrix}\n")

vertices_global_co()


Then I've found out that object matrix world starts to ignore object location after converting it to 3x3 matrix.

import bpy

def matrix_check():
obj = bpy.context.object
print(obj.matrix_world.decompose())
print(obj.matrix_world.to_3x3().to_4x4().decompose())

matrix_check()


So I have tried to sum the result with array filled with object location coordinates. I've tried to do it before and after multiplying it by the world matrix, but it doesn't give a correct result in either ways:

import bpy
import numpy as np

def vertices_global_co():
obj = bpy.context.object
verts = obj.data.vertices
mw = obj.matrix_world.to_3x3()
obj_matrix = np.matrix(mw)

# Store vertices to 1D numpy array
verts_co_1D = np.zeros([len(verts)*3], dtype='f')
verts.foreach_get("co", verts_co_1D)

# Convert 1D array to array of 3D arrays
verts_co_3D = np.zeros([len(verts), 3], dtype='f')
verts_co_3D[:, 0] = verts_co_1D[0::3]
verts_co_3D[:, 1] = verts_co_1D[1::3]
verts_co_3D[:, 2] = verts_co_1D[2::3]

# Get numpy array with object location coordinates
loc_array = np.zeros([len(verts), 3], dtype='f')
loc_array[:, 0].fill(obj.location[0])
loc_array[:, 1].fill(obj.location[1])
loc_array[:, 2].fill(obj.location[2])

reference_matrix = np.array([obj.matrix_world @ v.co for v in verts])

#    verts_co_3D += loc_array
verts_co_3D[:] = verts_co_3D @ obj_matrix
verts_co_3D += loc_array # you may comment this line and uncomment the same line above

print(f"Reference: {reference_matrix}\n")
print(f"Result: {verts_co_3D}\n")

vertices_global_co()


I even tried to make a copy of the object and its mesh, apply transforms and get vertices from the resulting mesh, but it is even slower than making it directly in a for loop. To set vertex coordinates to numpy array for 1 200 000 polygons object it takes about:

• .foreach_get() method: 0.007-0.008 seconds,
• for loop: 1,3-1,5 seconds
• get from temporal object and mesh copy: 3,5+ seconds I believe there should be a way to get the result with numpy and some linear algebra, but I've got not enough skills and knowledge for this. Can anyone help, please?

The mistake in the last example was in attempt to multiply coordinates by matrix while the proper way should be to multiply matrix by coordinates. I haven't figured out how to make it via Numpy, but the opposite way turned out to be to invert matrix first and then to use it for vertex coordinates multiplication. Also from here I've figured out that top left 3x3 values in the 4x4 matrix determine rotation of the vector, and the top 3 values in the right column determine location offset of the vector. So the proper way was to:

• Invert object matrix world
• Convert it to 3x3 matrix
• Multiply (get dot product of) vertices coordinates by the resulting matrix
• Add object location (first 3 values in the 4x4 matrix right column) to all vertices coordinates
import bpy
import numpy as np
import time

def vertices_global_co():
t1 = time.perf_counter()
obj = bpy.context.object
verts = obj.data.vertices
vlen = len(verts)
obj_matrix = np.matrix(obj.matrix_world.inverted().to_3x3())

verts_co_1D = np.zeros([vlen*3], dtype='f')
verts.foreach_get("co", verts_co_1D)
verts_co_3D = verts_co_1D.reshape(vlen, 3)

#    reference = np.array([obj.matrix_world @ v.co for v in verts])

verts_co_3D = verts_co_3D @ obj_matrix
verts_co_3D += np.array(obj.location)

#    print(f"Reference: {reference}\n")
print(f"Result: {verts_co_3D}\n")
print(f"Time: {time.perf_counter()-t1}")

vertices_global_co()


I've tried it only with rotation and location changes. The scale adjustment probably needs more operations.

UPD. Thanks to @scurest for the explanation of the difference between inverted and transposed matrices, for pointing at difference between np.zeros vs np.empty and for the given example in the comment. Here is the updated code which takes all objects transformations into account:

import bpy
from bpy.types import Object
import numpy as np
import time

def vertices_global_co(obj: Object) -> np.ndarray:
"""Return numpy array with object vertices global coordinates"""

rotation_and_scale = obj.matrix_world.to_3x3().transposed()  # rotation and scale matrix (mathutils.Matrix)
offset = obj.matrix_world.translation  # location offset vector (mathutils.Vector)
vertices_blender = obj.data.vertices  # Blender mesh vertices (bpy.types.MeshVertices)
vlen = len(vertices_blender)  # Number of vertices (int)

vertices_local = np.empty([vlen*3], dtype='f')  # Empty numpy 1D array, each value needs to be initialized
vertices_blender.foreach_get("co", vertices_local)  # Store each local coordinate of each vertex to empty 1D array
vertices_local = vertices_local.reshape(vlen, 3)  # Make 3D array with vertices local coordinates from 1D array
vertices_global = np.matmul(vertices_local, rotation_and_scale)  # multiply each vertex coordinates by given matrix
vertices_global += offset  # Apply location offset

#    # uncomment the following to compare with Blender matrix multiplication reference:
#    reference_matrix = np.array([obj.matrix_world @ v.co for v in verts])
#    print(f"Reference: {reference_matrix}\n")

return vertices_global

if __name__ == "__main__":
t1 = time.perf_counter()
result = vertices_global_co(bpy.context.object)
print(f"\nResult: {result}")
print(f"Time: {time.perf_counter()-t1} sec")

• See this. It's not the inverse but the transpose (it's a row-major/column-major conversion). The inverse only works for pure rotations (since for rotations the inverse is the transpose). Also for the translation you need the translation part of the world matrix, not ob.location. Btw, it may be slightly faster to use np.empty over np.zeros. Commented May 25, 2022 at 0:11
• Thank you! This is really useful, I've updated the answer. Commented May 25, 2022 at 9:07