# Unexpected result setting a shear matrix as transform

I am doing a visualization where I need to transform a sphere with a matrix. Easiest solution would be to just set the matrix as local transform but this gives me unexpected results.

I was trying around a little and found out, that setting matrix_local, delta_... and operating on vertex positions directly yields very different results. As an example, I tried it with a simple shear matrix. Here are the results:

Using matrix on vertex positions

Using local transformation matrix

Using delta transform This is the code:

import bpy
from mathutils import *

def transform_verts(context):
obj = context.active_object
mat = Matrix.Shear('XY', 4, (3,3))

#this does what it is supposed to do
for j in range(0, len(obj.data.vertices)):
obj.data.vertices[j].co = mat @ obj.data.vertices[j].co

def transform_delta(context):
obj = context.active_object

# This does NOT work as intended :(
# this deforms the sphere into an elongated Ellipsoide
d_loc, d_rot, d_sc = Matrix.Shear('XY', 4, (3,3)).decompose()
obj.delta_location = d_loc
obj.delta_rotation_quaternion = d_rot
obj.delta_scale = d_sc

def transform_matrix_local(context):
obj = context.active_object

# for unknown reasons it is not possible to do this with an object transformation matrix
# setting matrix_local yields similar weird results as setting delta transform
# I suppose it may have to do with Blender internally decomposing it
obj.matrix_local = Matrix.Shear('XY', 4, (3,3))

class SimpleOperator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.simple_operator"
bl_label = "Simple Object Operator"

@classmethod
def poll(cls, context):
return context.active_object is not None

def execute(self, context):
#transform_verts(context)
#transform_delta(context)
transform_matrix_local(context)
return {'FINISHED'}

def register():
bpy.utils.register_class(SimpleOperator)

def unregister():
bpy.utils.unregister_class(SimpleOperator)

if __name__ == "__main__":
register()

# test call
bpy.ops.object.simple_operator()


Why does setting the matrix_local also does something very different from what is intended? In my understanding, setting the matrix_local should do exactly the same as applying the matrix to vertex positions.

Is there a workaround for this issue that does not involve looping through vertices and setting the position manually? The above is just an example. My actual use case is a modal operator and right now I need to store the original vertex positions and loop through them each time modal is called. That is really slow compared to just setting the local transformation matrix. If there isn't, any idea how to speed it up?

Edit: obj.data.transform() is not possible because I need to apply the matrix to the sphere each time. This matrix is not necessarily invertable, so I can't just do the inverse transformation from the previous call and then do the current transformation. I would only be able to use it if I can somehow restore the positions the vertices had before the transformation.

Mesh.transform

Can transform mesh directly by a matrix with Mesh.transform(matrix) The matrix must be a 4x4. Use in object mode. Update the mesh to see result.

Python console code, C = bpy.context

>>> mat = Matrix.Shear('XY', 4, (3,3))
>>> mat
Matrix(((1.0, 0.0, 3.0, 0.0),
(0.0, 1.0, 3.0, 0.0),
(0.0, 0.0, 1.0, 0.0),
(0.0, 0.0, 0.0, 1.0)))

>>> C.object.data.transform(mat)
>>> C.object.data.update()


But this is transforms the vertices for good. In the modal operator, I need to apply it to the original positions of the sphere on each update. And the matrix is not necessarily invertable, so I can't just do the inverse beforehand, which makes it tricky

Shear matrix is not orthogonal, this is why it is not seen as an object matrix, and only in edit mode.

>>> mat.is_orthogonal
False


AFAIK will need to apply to verts to shear.

Some ways to copy mesh coords, swap out a mesh copy

>>> me = C.object.data
>>> me2 = C.object.data.copy()
>>> me2.transform(mat)
>>> C.object.data = me2 # see transform
>>> C.object.data = me # back to original
>>> bpy.data.meshes.remove(me2) # remove it when done


Foreach set and get

load the 3d vert coordinates into a ravelled array. Have used numpy as the matrix algebra could be performed on it reshaped, although will find Mesh.transform is quick too.

>>> import numpy as np
>>> data = np.zeros(len(me.vertices) * 3)
>>> me.vertices.foreach_get("co", data)
>>> data
array([ 0.00000000e+00,  1.95090324e-01,  9.80785251e-01, ...,
1.51397259e-08,  1.95089921e-01, -9.80785310e-01])


The data is ravelled, here it is reshaped to display the coordinates

>>> np.reshape(data, (-1, 3))
array([[ 0.00000000e+00,  1.95090324e-01,  9.80785251e-01],
[ 0.00000000e+00,  3.82683456e-01,  9.23879504e-01],
[ 0.00000000e+00,  5.55570245e-01,  8.31469595e-01],
...,
[ 1.52975474e-07,  7.07106352e-01, -7.07106769e-01],
[ 7.84696610e-08,  3.82682920e-01, -9.23879623e-01],
[ 1.51397259e-08,  1.95089921e-01, -9.80785310e-01]])


Transform the data, update to see result

>>> me.transform(mat)
>>> me.update()


write back, update to see result.

>>> me.vertices.foreach_set("co", data)
>>> me.update()


Another OTT method would be to make a shape key and transform that.

• But this is transforms the vertices for good. In the modal operator, I need to apply it to the original positions of the sphere on each update. And the matrix is not necessarily invertable, so I can't just do the inverse beforehand, which makes it tricky. Mar 29, 2020 at 13:46
• Copy the mesh on invoke? Or copy vert coords with foreach_get and write with foreach_set or similarly with a bmesh AFAIK shear is not applicable as an object matrix since the axes need to be orthogonal, hence no shear available outside of edit mode. Mar 29, 2020 at 13:59
• I didn't know about foreach_get and foreach_set! Thanks for the hint. Do you how to use it with vectors? The documentation states "Only works for ‘basic type’ properties (bool, int and float)!" Mar 29, 2020 at 14:12
• Thanks a bunch. I already spend hours trying to wrap my head around this problem with the vertex transformation Mar 29, 2020 at 14:49
• Pleasure. Thanks for updating Q on request. Good question. Mar 29, 2020 at 15:01