# Replace matrix @ vector list comprehensions with something more efficient

I'm looking to replace list comprehensions like the following with something more efficient and I was wondering if numpy can be used.

[obj.matrix_world @ v.co for v in obj.data.vertices]


I can do this, which is significantly faster, but still lacks the matrix multiplication, that needs to happen for each coordinate.

coords = np.empty((len(obj.data.vertices), 3), 'f')
obj.data.vertices.foreach_get('co', np.reshape(coords, len(obj.data.vertices) * 3))


I was hoping it would just be

np.matrix(obj.matrix_world) @ coords


But that fails with (FWIW, It's just a cube, hence (8, 3))

ValueError: shapes (4,4) and (8,3) not aligned: 4 (dim 1) != 8 (dim 0)


Similarly, doing

for co in coords:
np.matrix(obj.matrix_world) @ co


fails with

ValueError: shapes (4,4) and (3,) not aligned: 4 (dim 1) != 3 (dim 0)


So I've hit a wall now. Numpy can't multiply a 4x4 matrix with a 3d vector, but Blender can. How do I solve this in a numpy way?

edit:

Based on @JoseConseco's excellent reply below, this is what I'm doing now

mx = active.matrix_world
verts = active.data.vertices
vert_count = len(verts)

coords = np.empty((vert_count, 3), 'f')
verts.foreach_get('co', np.reshape(coords, vert_count * 3))

coords_4d = np.ones((vert_count, 4), 'f')
coords_4d[:, :-1] = coords

coords = np.einsum('ij,aj->ai', mx, coords_4d)[:, :-1]


Note, that there's no need to convert the (mathutils) Matrix into an np array. It works fine just like that.

Based on my tests, doing this via numpy is ~3 times faster than the list comprehension.

• Even though your problem takes place in Blender, it's a generic math problem and there is a StackExchange for math. I imagine that you will have an easier time finding linear algebra guru's over yonder ;) There will be some here too of course, no offense intended :) math.stackexchange.com – MarcClintDion May 1 '19 at 10:07
• Maybe yes, but I don't speak their language :) and felt this should be a common enough (or at east useful) issue in Blender circles. – MACHIN3 May 1 '19 at 11:44
• I'm voting to close this question as off-topic because it is a general Python problem which isn't specific to Blender. – Ray Mairlot May 1 '19 at 12:03
• I think the question is fine. I would suggest editing title, such that it is more akin to the question "replace matrix @ vector list comprehensions with something more efficient" – batFINGER May 1 '19 at 12:23

Quickest way AFAIK:

mat = np.array(self.curveObj.matrix_world)

verts_co = np.zeros((vertCount*3), dtype=np.float)
mesh.vertices.foreach_get("co", verts_co)
verts_co.shape = (vertCount, 3)
verts_co_4d = np.ones(shape=(vertCount, 4), dtype=np.float)
verts_co_4d[:, :-1] = verts_co  # cos v (x,y,z,1) - point,   v(x,y,z,0)- vector
splinePoints_world = np.einsum('ij,aj->ai', mat, splinePoints)


np.einsum('ij,aj->ai', mx, coords_4d)[:, :-1]


into

np.dot(coords_4d, mx.inverted())[:, :-1]


Should improve performance.

The entire script:

vlen = len(obj.data.vertices)

vco = np.empty(vlen * 3, 'f')
obj.data.vertices.foreach_get('co', vco)

coords = np.empty((vlen, 4), 'f')
coords[::4] = 1.0
coords[:,:-1] = vco.reshape((vlen, 3))

coords = np.dot(coords, obj.matrix_world)
res = coords[:,:-1].reshape((vlen*3))

obj.data.vertices.foreach_set('co', res)


reshape is basically free operation in Numpy, so should be used liberally. np.empty also just allocates memory, doesn't write it so maybe saves some time.

• Thank ambi, I appreciate you chiming in! I'm also using empty and reshape. I've also just noticed that float64 seems to be faster than float32. I still need float32(need coords for gpu drawing) at the end, so I just change it at the end, and it's still faster. As for, np.dot() I'm actually getting a different result from it than via the einsum() method. I need to study this some more. Thanks again, much appreciated! – MACHIN3 May 2 '19 at 9:13

Mesh.transform(Matrix)

As an alternative to using numpy

The mesh transform method does exactly this, internally with the passed matrix.

Without knowing what you intend to do, after transforming have loaded a flat list of verts using foreach_get , and removed the mesh copy.

Not loading the transformed copy mesh coordinates into a list speeds it up considerably. The copy mesh will have the same vertex indices as the original

Even with the extra overhead it is quicker than doing the matrix multiplication with python (via blender mathutils) individually.

Test code. Using the transform method, with the foreach get and remove overhead is > 10X quicker than list comprehension. Using the (edited to test) numpy version from @JoseConseco 's answer is around 2 to 3X quicker than the transform method.

import bpy

from time import time

ob = bpy.context.object
t = time()
me = ob.data.copy()
me.transform(ob.matrix_world)
# dont really need to do this since the verts co is alread transformed
verts = [0] * 3 * len(me.vertices)
me.vertices.foreach_get("co", verts)
bpy.data.meshes.remove(me)  # clean up
copy_time = time() - t

t = time()
verts = [ob.matrix_world @ v.co for v in ob.data.vertices]
listcomp_time = time() - t

print(listcomp_time / copy_time)
import numpy as np
t = time()
mat = np.array(ob.matrix_world)

verts_co = np.zeros((len(ob.data.vertices) * 3), dtype=np.float)
ob.data.vertices.foreach_get("co", verts_co)
verts_co.shape = (len(ob.data.vertices), 3)
verts_co_4d = np.ones(shape=(len(ob.data.vertices), 4), dtype=np.float)
verts_co_4d[:, :-1] = verts_co  # cos v (x,y,z,1) - point,   v(x,y,z,0)- vector
splinePoints_world = np.einsum('ij,aj->ai', mat, verts_co_4d)
numpy_time = time() - t

print(numpy_time / copy_time)

• Thanks, that's really interesting to use mesh.transform(). It is indeed incredibly fast and I've started to use it in some other areas now. – MACHIN3 May 2 '19 at 8:25