How to decompose a matrix in Python for Maya? - python

I'm trying to write a script that can transfer translate and rotate from child to parent and vice versa without having to resort on parent constraints. I've spent the last few days investigating this using matrices information but I'm a beginner in Python and matrices.
So far, I've been able to find the matrices I want and apply the correct calculations on it but I can't convert those matrices back to regular Maya values. Essentially I'm trying to replicate whatever it is the "decomposeMatrix" node does in Maya.
After a lot of research and testing, I'm pretty sure the MTransformationMatrix function from OpenMaya is what I need but I can't make it work, I don't know how to write/use it, what parameters it's using, etc.
If I am on the right track, what do I need to finish the work? If I'm tackling something bigger that requires a lot more coding than this, I'd be interested to understand more on that too.
Here's the code:
import maya.cmds as mc
import maya.OpenMaya as OpenMaya
def myM():
tlm = MMatrix(cmds.getAttr('TRAJ.matrix'))
pwm = MMatrix(cmds.getAttr('TRAJ.worldMatrix'))
pim = MMatrix(cmds.getAttr('TRAJ.parentInverseMatrix'))
prod = tlm * pwm * pim
return prod
tMat = OpenMaya.MTransformationMatrix(prod)
print tMat
myM()
Edit
I misused return on the code above. Re-testing the code today (and also when implementing Klaudikus' suggestions) I get the following error:
Error: TypeError: file /home/Maya_2020_DI/build/RelWithDebInfo/runTime/lib/python2.7/site-packages/maya/OpenMaya.py line 7945: in method 'new_MMatrix', argument 1 of type 'float const [4][4]' #

I'm assuming you understand what the script is doing, but I'll recap just in case:
Getting the local matrix values of TRAJ and creating a new MMatrix object from those values.
Getting the world matrix values of TRAJ and creating a new MMatrix object from those values.
Getting the parent inverse matrix of TRAJ and creating a new MMatrix object from those values.
Multiplying all three of the above matrices and storing the resulting MMatrix in prod.
Returning prod.
As stated by halfer, the rest of the function is ignored because return will exit the function. Here's something that should work.
# here you typed:
# import maya.cmds as mc
# but further below used the cmds.getAttr...
# I decided to change the import statement instead
import maya.cmds as cmds
# MMatrix is a class within OpenMaya
# when using it, you must reference the module first then the class/object as so:
# my_matrix = OpenMaya.MMatrix()
import maya.OpenMaya as OpenMaya
def myM():
tlm = OpenMaya.MMatrix(cmds.getAttr('TRAJ.matrix'))
pwm = OpenMaya.MMatrix(cmds.getAttr('TRAJ.worldMatrix'))
pim = OpenMaya.MMatrix(cmds.getAttr('TRAJ.parentInverseMatrix'))
prod = tlm * pwm * pim
# check the documentation at:
# https://help.autodesk.com/view/MAYAUL/2020/ENU/?guid=__py_ref_class_open_maya_1_1_m_transformation_matrix_html
# here I'm "creating" an MTransformationMatrix with the MMatrix stored in prod
tMat = OpenMaya.MTransformationMatrix(prod)
# this is how you get the data from this object
# translation is an MVector object
translation = tMat.translation(OpenMaya.MSpace.kObject)
# eulerRotation is a MEulerRotation object
eulerRotation = tMat.rotation(asQuaternion=False)
# typically you'll return these values
return translation, eulerRotation
TRAJ_trans, TRAJ_rot = myM()
But this is not really useful. First of all the function myM should really take arguments so you can reuse it and get the translation and rotation from any object in the scene, not just TRANJ. Second, the math you are performing doesn't make sense to me. I'm not a mathematician, but here you're multiplying the local matrix, by the world matrix, by the parent's inverse local matrix. I honestly wouldn't be able to tell you what this does, but as far as I know, it's most likely useless. I won't go into detail on matrices, as it's a beast of a subject and you seem to be on that path anyway, but in a nutshell:
obj_local_matrix = obj_world_matrix * inverse_parent_world_matrix
obj_world_matrix = obj_local_matrix * parent_world_matrix
Another way of writing this is:
obj_relative_to_other_matrix = obj_world_matrix * inverse_other_world_matrix
obj_world_matrix = obj_relative_to_other_matrix * other_world_matrix
This is essentially the math that dictates the relationship between parented nodes. However, you can get the transform values of one node relative to any other node, irrespective of it's parenting, as long as you have their world matrices. Note that order is important here, as matrix multiplication is not commutative A * B != B * A.
What I gather from your question is that you're trying to get the relative transforms of an object, to that of another object in a scene. The following function returns the position, rotation, and scale of an object relative to that of another object.
def relative_trs(obj_name, other_obj_name):
"""
Returns the position, rotation and scale of one object relative to another.
:param obj_name:
:param other_obj_name:
:return:
"""
obj_world_matrix = OpenMaya.MMatrix(cmds.getAttr('{}.worldMatrix'.format(obj_name)))
other_obj_world_matrix = OpenMaya.MMatrix(cmds.getAttr('{}.worldMatrix'.format(other_obj_name)))
# multiplying the world matrix of one object by the inverse of the world matrix of another, gives you it's relative matrix of the object to the other
matrix_product = obj_world_matrix * other_obj_world_matrix.inverse()
trans_matrix = OpenMaya.MTransformationMatrix(matrix_product)
translation = trans_matrix.translation(OpenMaya.MSpace.kObject)
euler_rotation = trans_matrix.rotation(asQuaternion=False)
scale = trans_matrix.scale(OpenMaya.MSpace.kObject)
return list(translation), list(euler_rotation), list(scale)
You can test the function by creating two hierarchies, and run the function between a leaf node of one hierarchy and any node in the other hierarchy. Store the results. Then re-parent the leaf node object to the one in the other hierarchy and check its local coordinates. They should match your stored results. This is essentially how a parent constraint works.

Thanks to Klaudikus and a bunch of perseverance, I managed to make it work. It may look bit wanky but that's good enough for me! It does needs user friendly optimisation of course (that's my next step) but the core idea works. It potentially makes exchanging objects location between child and parent way faster than snapping or parent constraining.
import maya.cmds as cmds
from maya.api.OpenMaya import MMatrix
import pymel.core as pm
def myM():
tlmList = cmds.getAttr('TRAJ.matrix')
tlm = MMatrix(tlmList)
pwmList = cmds.getAttr('PARENT1.worldMatrix')
pwm = MMatrix(pwmList)
pimList = cmds.getAttr('PARENT0.worldInverseMatrix')
pim = MMatrix(pimList)
prod = tlm * pwm * pim
pm.xform('PARENT1', m=([prod[0],prod[1],prod[2],prod[3],prod[4],prod[5],prod[6],prod[7],prod[8],prod[9],prod[10],prod[11],prod[12],prod[13],prod[14],prod[15]]))
myM()

Related

Python: igraph - set vertexseq none value to a specific one

I want to learn more about igraph libary. I want to make a simple graph with two nodes labeled at different time. In igraph tutorial I have found its possible to label the list at all after I generated the graph. It works with code:
from igraph import *
p = Graph()
p.add_vertices(1)
p.vs["label"] = ["test", "test1"]
layout = p.layout("kk")
plot(p, layout=layout)
but what, if I want to label the graph step by step. That means I first want add some vertices then label it. Then perform some calculation add some other vertices and label that other on after I added it without loosing the first ones. I just tried it with following code, but for some reason it does not work as I want. How can I solve it?
from igraph import *
p = Graph()
p.add_vertices(1)
p.vs["label"] = ["test"]
p.add_vertices(1)
p.vs["label"][1] = "test1"
layout = p.layout("kk")
plot(p, layout=layout)
g.vs["label"][1] = "test1" essentially boils down to this:
labels = g.vs["label"]
labels[1] = "test1"
Now, g.vs["label"] extracts the label attribute of all the vertices, constructs a Python list, fills the attributes into the list and then returns the list to you. If you run type(g.vs["label"]), you can see that labels is an ordinary Python list. Since it is not a view into the internal data structure, any modifications that you make to the list will not be reflected in the graph itself.
On the other hand, g.vs[1]["label"] = "test1" will do something like this:
vertex = g.vs[1]
vertex["label"] = "test1"
and this will work because g.vs[1] is a Vertex object (you can check it with type(g.vs[1]), and using the Vertex object as a dictionary will update the underlying attributes.
Footnote: in theory, g.vs["label"] could return some kind of a proxy object that represents the "label" attribute of each vertex and that writes any changes back to the underlying graph. However, it has not been implemented this way in earlier versions of the igraph Python interface and we cannot change it now without potentially breaking code of people who rely on the fact that g.vs["label"] returns a copy.

Using Python Ray With CPLEX Model Object

I am trying to parallelize an interaction with a Python object that is computationally expensive. I would like to use Ray to do this but so far my best efforts have failed.
The object is a CPLEX model object and I'm trying to add a set of constraints for a list of conditions.
Here's my setup:
import numpy as np
import docplex.mp.model as cpx
import ray
m = cpx.Model(name="mymodel")
def mask_array(arr, mask_val):
array_mask = np.argwhere(arr == mask_val)
arg_slice = [i[0] for i in array_mask]
return arg_slice
weeks = [1,3,7,8,9]
const = 1.5
r = rate = np.array(df['r'].tolist(), dtype=np.float)
x1 = m.integer_var_list(data_indices, lb=lower_bound, ub=upper_bound)
x2 = m.dot(x1, r)
#ray.remote
def add_model_constraint(m, x2, x2sum, const):
m.add_constraint(x2sum <= x2*const)
return m
x2sums = []
for w in weeks:
arg_slice = mask_array(x2, w)
x2sum = m.dot([x2[i] for i in arg_slice], r[arg_slice])
x2sums.append(x2sum)
#: this is the expensive part
for x2sum in x2sums:
add_model_constraint.remote(m, x2, x2sum, const)
In a nutshell, what I'm doing is creating a model object, some variables, and then looping over a set of weeks in order to build a constraint. I subset my variable, compute some dot products and apply the constraint. I would like to be able to create the constraint in parallel because it takes a while but so far my code just hangs and I'm not sure why.
I don't know if I should return the model object in my function because by default the m.add_constraint method modifies the object in place. But at the same time I know Ray returns references to the remote value so yea, not sure what's supposed to happen there.
Is this at all a valid use of ray? It it reasonable to expect to be able to modify a CPLEX object in this way (or any other arbitrary python object)?
I am new to Ray so I may be structuring this all wrong, or maybe this will never work for X, Y, and Z reason which would also be good to know.
The Model object is not designed to be used in parallel. You cannot add constraints from multiple threads at the same time. This will result in undefined behavior. You will have to at least a lock to make sure only thread at a time adds constraints.
Note that parallel model building may not be a good idea at all: the order of constraints will be more or less random. On the other hand, behavior of the solver may depend on the order of constraints (this is called performance variability). So you may have a hard time reproducing certain results/behavior.
I understand the primary issue was the performance of module building.
From the code you sent, I have two suggestions to address this:
post constraints in batches, that is store constraints in a list and add them once using Model.add_constraints(), this should be more efficient than adding them one at a time.
experiment with Model.dotf() (functional-style scalar product). It avoids building auxiliary lists, passing instead a function of the key , returning the coefficient.
This method is new in Docplex version 2.12.
For example, assuming a list of 3 variables:
abc = m.integer_var_list(3, name=["a", "b", "c"]) m.dotf(abc, lambda
k: k+2)
docplex.mp.LinearExpression(a+2b+3c)
Model.dotf() is usually faster than Model.dot()

Generating random numbers for a probability density function in Python

I'm currently working on a project relating to brownian motion, and trying to simulate some of it using Python (a language I'm admittedly very new at). Currently, my goal is to generate random numbers following a given probability density function. I've been trying to use the scipy library for it.
My current code looks like this:
>>> import scipy.stats as st
>>> class my_pdf(st.rv_continuous):
def _pdf(self,x,y):
return (1/math.sqrt(4*t*D*math.pi))*(math.exp(-((x^2)/(4*D*t))))*(1/math.sqrt(4*t*D*math.pi))*(math.exp(-((y^2)/(4*D*t))))
>>> def get_brown(a,b):
D,t = a,b
return my_pdf()
>>> get_brown(1,1)
<__main__.my_pdf object at 0x000000A66400A320>
All attempts at launching the get_brown function end up giving me these hexadecimals (always starting at 0x000000A66400A with only the last three digits changing, no matter what parameters I give for D and t). I'm not sure how to interpret that. All I want is to get random numbers following the given PDF; what do these hexadecimals mean?
The result you see is the memory address of the object you have created. Now you might ask: which object? Your method get_brown(int, int) calls return my_pdf() which creates an object of the class my_pdf and returns it. If you want to access the _pdf function of your class now and calculate the value of the pdf you can use this code:
get_brown(1,1)._pdf(x, y)
On the object you have just created you can also use all methods of the scipy.stats.rv_continous class, which you can find here.
For your situation you could also discard your current code and just use the normal distribution included in scipy as Brownian motion is mainly a Normal random process.
As noted, this is a memory location. Your function get_brown gets an instance of the my_pdf class, but doesn't evaluate the method inside that class.
What you probably want to do is call the _pdf method on that instance, rather than return the class itself.
def get_brown(a,b):
D,t = a,b # what is D,t for?
return my_pdf()_pdf(a,b)
I expect that the code you've posted is a simplification of what you're really doing, but functions don't need to be inside classes - so the _pdf function could live on it's own. Alternatively, you don't need to use the get_brown function - just instantiate the my_pdf class and call the calculation method.

Retrieving CATVariants from CATIA in Python

I am interfacing CATIA with Python in order to obtain the inertia matrix of some body. When arriving to the inertia matrix object, which is labeled as:
bound method GetInertiaMatrix of COMObject Item
I am unable to obtain any components of the matrix. I have been reading throughout whole internet and there is some people with the same problem than me, but it seems unsolved still. The code to obtain the inertia matrix object is listed below, in which obj_part refers to the part object, obj_doc refers to the part object document, and so on.
obj_ref = obj_part.CreateReferenceFromObject(body_to_measure);
obj_SPA = obj_doc.GetWorkBench("SPAWorkbench");
obj_measurable = obj_SPA.GetMeasurable(obj_ref);
obj_inertias = obj_SPA.Inertias;
obj_inertias.Add(body_to_measure);
obj_inertia = obj_inertias.Item(1);
inertia_matrix = obj_inertia.GetInertiaMatrix;

Force matrix_world to be recalculated in Blender

I'm designing an add-on for blender, that changes the location of certain vertices of an object. Every object in blender has a matrix_world atribute, that holds a matrix that transposes the coordinates of the vertices from the object to the world frame.
print(object.matrix_world) # unit matrix (as expected)
object.location += mathutils.Vector((5,0,0))
object.rotation_quaternion *= mathutils.Quaternion((0.0, 1.0, 0.0), math.radians(45))
print(object.matrix_world) # Also unit matrix!?!
The above snippet shows that after the translation, you still have the same matrix_world. How can I force blender to recalculate the matrix_world?
You should call Scene.update after changing those values, otherwise Blender won't recalculate matrix_world until it's needed [somewhere else]. The reason, according to the "Gotcha's" section in the API docs, is that this re-calc is an expensive operation, so it's not done right away:
Sometimes you want to modify values from python and immediately access the updated values, eg:
Once changing the objects bpy.types.Object.location you may want to access its transformation right after from bpy.types.Object.matrix_world, but this doesn’t work as you might expect.
Consider the calculations that might go into working out the objects final transformation, this includes:
animation function curves.
drivers and their pythons expressions.
constraints
parent objects and all of their f-curves, constraints etc.
To avoid expensive recalculations every time a property is modified, Blender defers making the actual calculations until they are needed.
However, while the script runs you may want to access the updated values.
This can be done by calling bpy.types.Scene.update after modifying values which recalculates all data that is tagged to be updated.
Calls to bpy.context.scene.update() can become expensive when called within a loop.
If your objects have no complex constraints (e.g. plain or parented), the following can be used to recompute the world matrix after changing object's .location, .rotation_euler\quaternion, or .scale.
def update_matrices(obj):
if obj.parent is None:
obj.matrix_world = obj.matrix_basis
else:
obj.matrix_world = obj.parent.matrix_world * \
obj.matrix_parent_inverse * \
obj.matrix_basis
Some notes:
Immediately after setting object location/rotation/scale the object's matrix_basis is updated
But matrix_local (when parented) and matrix_world are only updated during scene.update()
When matrix_world is manually recomputed (using the code above), matrix_local is recomputed as well
If the object is parented, then its world matrix depends on the parent's world matrix as well as the parent's inverse matrix at the time of creation of the parenting relationship.
I needed to do this too but needed this value to be updated whilst I imported a large scene with tens of thousands of objects.
Calling 'scene.update()' became exponentially slower, so I needed to find a way to do this without calling that function.
This is what I came up with:
def BuildScaleMatrix(s):
return Matrix.Scale(s[0],4,(1,0,0)) * Matrix.Scale(s[1],4,(0,1,0)) * Matrix.Scale(s[2],4,(0,0,1))
def BuildRotationMatrixXYZ(r):
return Matrix.Rotation(r[2],4,'Z') * Matrix.Rotation(r[1],4,'Y') * Matrix.Rotation(r[0],4,'X')
def BuildMatrix(t,r,s):
return Matrix.Translation(t) * BuildRotationMatrixXYZ(r) * BuildScaleMatrix(s)
def UpdateObjectTransform(ob):
ob.matrix_world = BuildMatrix(ob.location, ob.rotation_euler, ob.scale)
This isn't most efficient way to build a matrix (if you know of a better way in blender, please add) and this only works for XYZ order transforms but this avoids exponential slow downs when dealing with large data sets.
Accessing Object.matrix_world is causing it to "freeze" even though you don't do anything to it, eg:
m = C.active_object.matrix_world
causes the matrix to be stuck. Whenever you want to access the matrix use
Object.matrix_world.copy()
only if you want to write the matrix, use
C.active_object.matrix_world = m

Categories