# Problem with Non-Typical Matrices

Hello. I have some problems with applying matrix transformation to object. What wrong. I was trying to “broke” a matrix by world scale.
Forst of all, I take the matrix and multiply it to a non-proportional matrix. And I’m expecting behavior like in edit mode - object gonna be a little bit deformed. But blender trying to fix the matrix. How can I apply a non-proportional matrix? Thank you.
How to reproduce:

1.Create some object (in my case its cube)
2.Take a random rotation
3.Do this code:

``````import bpy
from mathutils import Matrix

m  = bpy.data.objects["Cube"].matrix_world

superMatrix  = Matrix(((4.0, 0.0,  0.0, 0.0),
(0.0, 1.0, 0.0, 0.0),
(0.0, 0.0, -1.0, 0.0),
(0.0, 0.0, 0.0, 1.0)))

bpy.data.objects["Cube"].matrix_world= m @ superMatrix
``````

Expecting object scaling by world coordinate X
But object are scaling by local X

Probably you want to change the order?

``````bpy.data.objects["Cube"].matrix_world= superMatrix @ m
``````

The transform matrices are applied in reverse order.

I want something like this:

In case of reversing the order, object is scaling local-like coordinate too : (

Ah, I don’t think this is actually supported by Blender currently. Objects have location, rotation and scale, but for what you want there would need to be a shear component as well.

Multiplying with `matrix_world` will do its best to convert the matrix to location, rotation and scale values, but any shear will be lost. Shear is possible indirectly through parenting and constraints, but not directly as part of the Object transform parameters.

Thank you. Sad news( But can I’m expecting some improvement of this part of matrices?

I don’t know of immediate plans to add shearing support for object transforms.

It’s possible to shear using a parent matrix that’s non uniformly scaled and a child object matrix that’s rotated, although I wouldn’t know how to write it off the bat :s
However it’s always going to be baked in the object data (vertices) since like Brecht said there is no shear component so it can’t be represented at the object level

Recently, I’ve had to disabuse myself of the cherished belief that the equal sign (`=`) in Python just assigns a right hand value or reference to a left hand label. Depending on the prevailing environment, it can do more or do differently. In Blender, for left hand sides that expose RNA properties, `=` is adroitly overloaded; it is not a simple assignment operator.

``````# Let's make y-shear!
>>> yshear_mw
#~ Matrix(((1.0, 0.0, 0.0, 2.0),
#~         (0.25, 0.25, 0.0, 2.0),
#~         (0.0, 0.0, 0.125, 2.0),
#~         (0.0, 0.0, 0.0, 1.0)))
``````

Expected:

``````>>> for k in range(-5, 6, 1):
yshear_mw @ Vector((k, 0, 0))

#~ Vector((-3.0, 0.75, 2.0))
#~ Vector((-2.0, 1.0, 2.0))
#~ Vector((-1.0, 1.25, 2.0))
#~ Vector((0.0, 1.5, 2.0))
#~ Vector((1.0, 1.75, 2.0))
#~ Vector((2.0, 2.0, 2.0))
#~ Vector((3.0, 2.25, 2.0))
#~ Vector((4.0, 2.5, 2.0))
#~ Vector((5.0, 2.75, 2.0))
#~ Vector((6.0, 3.0, 2.0))
#~ Vector((7.0, 3.25, 2.0))
``````

That is, for input geometry, points equally spaced on the x-axis:

1. Data translates x = +2, y = +2, z= +2
2. Step along x: +1.00
3. Step along y: +0.25, shear component
4. No step apparent along z as input points showed no z-ward displacement and there is no shear component along z.

All is well. Let’s assign:

``````>>> bpy.data.objects['Child'].matrix_world = yshear_mw
>>> bpy.data.objects['Child'].matrix_world
#~ Matrix(((1.023111343383789, -0.030431389808654785, 0.0, 2.0),
#~         (0.125471830368042, 0.24814094603061676, 0.0, 2.0),
#~         (0.0, 0.0, 0.125, 2.0),
#~         (0.0, 0.0, 0.0, 1.0)))
#~
``````

Ah me. Not the same matrix. Spot check. Compare with above:

``````>>> bpy.data.objects['Child'].matrix_world @ Vector((-5, 0, 0))
#~ Vector((-3.1155567169189453, 1.37264084815979, 2.0))
>>> bpy.data.objects['Child'].matrix_world @ Vector(( 5, 0, 0))
#~ Vector((7.115556716918945, 2.62735915184021, 2.0))
``````

Not even close. What happened?

TL;DR: `=` won’t always do what you think it will do.

Tell me more: `=` can be overloaded in certain environments. In the Blender Python API, it is overloaded in such a way so that when some attempt is made to change the RNA property `matrix_world` on an object, other parts of the object related to UI-oriented `gets()` and `sets()`, representing rotations, translations and scalings, are bought into line with the proposed transform. That pipeline is optimized along a simplified decomposition scheme that accommodates rotations, scalings and translations. It executes very quickly, keeping UI framerates decently quick. But some kinds of matrices - like shears - don’t fit the simplified decomposition scheme and data are lost.

This lost is manifested in matrix assignments that don’t seem to assign as expected.

Rainy Day Reading: To get a sense of what happens in that deceptively simple seeming `=` assignment:

1. Take a debug-built Blender running under GNU’s `gdb` or some other IDE.
2. Run Blender itself with an addon that (at least) creates a
new mesh object and tries to position it by setting `matrix_world`.
3. Put `pdb` tripwires - `pdb.set_trace()` - just before the naïve `matrix_world` assignment.
4. On gnu debugger side, breakpoints have been set in select functions.
See commentary for particulars.

Dropping into Python just after `pdb.set_trace()` `pdb` prompts are `(Pdb)`, GNU prompts are `(gdb)`.

``````> /home/gosgood/.config/blender/2.93/scripts/addons/kkut/__init__.py(273)execute()
-> ob.matrix_world = Matrix(yshear_mw)
(Pdb) ob.name
'Child_loop'
``````

Stepping past the Python-level assignment:

``````(Pdb) n
[Switching to Thread 0x7fffe5646640 (LWP 13542)]
Thread 1 "blender" hit Breakpoint 1, rna_Object_internal_update(UNUSED_bmain=0x7fff8583a038, UNUSED_scene=0x7fffce3b9438, ptr=0x7fff82c2aa10) at /home/gosgood/git_repositories/blender/source/blender/makesrna/intern/rna_object.c:332
332	  DEG_id_tag_update(ptr->owner_id, ID_RECALC_TRANSFORM);
(gdb) c
``````

(Very) roughly, rna_Object_internal_update(Main *bmain, Scene *scene, PointerRNA *ptr) takes the matrix on the right hand side of the Python-level assignment and sets the following object fields as needed:

1. `ob->loc`,
2. `ob->scale`,
3. `ob->rot`

That’s phase one. Laying groundwork for phase two, `rna_Object_internal_update()` also tags `Child_loop` for downstream depsgraph re-evaluation. That deferred activity takes these three object fields as inputs and rebuilds afresh `ob->obmat`, from which properties `matrix_basis` and `matrix_world` derive.

Hit ‘continue’ at gdb level. At Python level, single-stepping through the script following the assignment…

``````> /home/gosgood/.config/blender/2.93/scripts/addons/kkut/__init__.py(274)execute()
(Pdb) n
-> if lclmode == 'EDIT' :
(Pdb) n
-> bpy.ops.object.editmode_toggle()
(Pdb) n
``````

Ding! Toggling edit mode kicks off the aforementioned depsgraph re-evaluation.

``````(gdb) p ob->id.name
\$1 = "OBChild_loop", '\000' <repeats 53 times>

``````

Behind the curtain, depsgraph evaluation generates the `matrix_basis` RNA object property from the enumerated object fields listed above. It is the first part of that quick, simplified synthesis scheme that optimizes for scalings, rotations and translations, but cannot generally synthesize all types of matrices.

Rather than the literal `gdb` step-through - a bit long and tedious - here’s a list in order of visitation:

1. BKE_object_eval_local_transform(Depsgraph *depsgraph, Object *ob)
a. BKE_object_to_mat4(Object *ob, float r_mat)
I. BKE_object_to_mat3(Object *ob, float r_mat)
i. BKE_object_scale_to_mat3(Object *ob, float mat)
ii. BKE_object_rot_to_mat3(const Object *ob, float mat, bool use_drot)
b. After scaling and rotation, translation is handled at the tail end of BKE_object_to_mat4(): `add_v3_v3v3(r_mat, ob->loc, ob->dloc);`

2. If an object is parented (`ob->parent` is populated) `matrix_basis` is composited with parent object transforms, leading to `matrix_world`: BKE_object_eval_parent(Depsgraph *depsgraph, Object *ob). Otherwise, `matrix_basis` == `matrix_world`

That is what follows, direct and deferred, from a ‘simple’ assignment to `matrix_world` at the Python level.

Well, there is the Shear tool…. Perhaps you would prefer not to change the mesh itself, favoring object-level transform. Or you have a more general case: an exotic matrix of some sort that is known to be mangled, given reasons above, on assignment to `matrix_world`.

In that case, Singular Value Decomposition to the rescue. Also spend a rainy day with Press, WH; Teukolsky, SA; Vetterling, WT; Flannery, BP (2007), “Section 2.6”, Numerical Recipes: The Art of Scientific Computing (3rd ed.), New York: Cambridge University Press.

Lets work out a practical example using a simple y shear (from Blender Python console):

``````>>> import numpy as np
>>>
>>> yshear = np.identity(3)
>>> yshear[1,0] = 3.0
>>> yshear
array([[1., 0., 0.],
[3., 1., 0.],
[0., 0., 1.]])
``````

The game is to use SVD to re-express `yshear` as a composition of three matrices: U a ‘pure’ rotation (no scaling), S, a ‘pure’ scaling (no rotation) and VH, another ‘pure’ rotation. Being ‘pure’ rotations and scalings, these components can individually glide through Blender without difficulty, while the composite shear gets mangled. See the nice diagram accompanying the Wikipedia article.

My ‘SVD’ pipeline only needs to solve for the 3 x 3 upper-left submatrix of the full-bore 4 x 4 homogeneous matrix, the rotation and scaling part. I’ll add the translation component post-SVD.

``````>>> u, s, vh = np.linalg.svd(yshear)
>>> u
array([[-0.28978415,  0.        , -0.95709203],
[-0.95709203,  0.        ,  0.28978415],
[ 0.        ,  1.        ,  0.        ]])

>>> s
array([3.30277564, 1.        , 0.30277564])

>>> vh
array([[-0.95709203, -0.28978415, -0.        ],
[ 0.        ,  0.        ,  1.        ],
[-0.28978415,  0.95709203,  0.        ]])
``````

Unlike the other two 3 x 3 rotation matrices, `s` is a simple vector containing the `singular values` diagonal.

Repackaging to homogeneous matrices entail setting the upper 3x3 quadrant of 4x4 identity matrices, to wit:

``````# XXX      XXX0
# XXX  --> XXX0
# XXX      XXX0
#          0001
mu               = np.identity(4)
ms               = np.identity(4)
mvh              = np.identity(4)
THREE             = slice(None,-1, 1)
mu[THREE,THREE]  = u
ms[THREE,THREE]  = np.diag(s, 0)
mvh[THREE,THREE] = vh
``````

As @Hadriscus suggests, the game continues with setting the `matrix_world` properties of three `Empty` parents, then parenting the mesh object to the last Empty on the chain. The flavor of parenting to use is `Object (without inverse)` so that the objects on the chain immediately reposition in response to parenting, without Blender generating the compensatory inverse matrix.

Off camera, create Empties `MU`, `MS`, `MVH` equivalents `mu, ms, mvh`
You now should be able to do something like this:

``````>>> bpy.data.objects['MU'].matrix_world  = Matrix(mu)
>>> bpy.data.objects['MS'].matrix_world  = Matrix(ms)
>>> bpy.data.objects['MVH'].matrix_world = Matrix(mvh)
>>>
>>> bpy.data.objects['MU'].matrix_world
#~ Matrix(((-0.28978410363197327, -4.1835821917857174e-08, -0.9570920467376709, 0.0),
#~         (-0.9570920467376709, 1.2666866666677379e-08, 0.28978410363197327, 0.0),
#~         (0.0, 1.0, -4.371138828673793e-08, 0.0),
#~         (0.0, 0.0, 0.0, 1.0)))
#~
>>>
>>> bpy.data.objects['MS'].matrix_world
#~ Matrix(((3.3027756214141846, 0.0, 0.0, 0.0),
#~         (0.0, 1.0, 0.0, 0.0),
#~         (0.0, 0.0, 0.30277565121650696, 0.0),
#~         (0.0, 0.0, 0.0, 1.0)))

>>>
>>> bpy.data.objects['MVH'].matrix_world
#~ Matrix(((-0.9570920467376709, -0.28978410363197327, -1.2666865778498959e-08, 0.0),
#~         (0.0, -4.371138828673793e-08, 1.0, 0.0),
#~         (-0.28978410363197327, 0.9570920467376709, 4.1835821917857174e-08, 0.0),
#~         (0.0, 0.0, 0.0, 1.0)))
#~
>>>
``````

Rounding errors introduce noise on the order of `1.0e-07`, but apart from that, the `matrix_world`'s generally reproduce the components garnered from SVD, unmangled.

The game concludes with the parenting (without inverse):

1. `MS` to `MU` - the unparented root
2. `MVH` to `MS`
3. `Child` to `MVH` - See the screen shot

This is a top view, peering down `+Z` to the origin. `Limit` brackets at the original unsheared geometry width, this to convince myself that I was obtaining a shear and not a sneaky rotation.

A PITA? You bet, there is no end to assigning the wrong matrix to the wrong Empty or parenting out of order. For shearing, the Shear tool invites. For other exotics, consider this approach, as SVD is a
general purpose matrix deconstructor. I suppose one could make a small library of these exotic transformation parenting chains for quick deployment.

Sorry for the length; hope this is a help.

4 Likes