Retrieving CATVariants from CATIA in Python - 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;

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.

How to decompose a matrix in Python for Maya?

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()

Getting (t, c, k) values from OpenCascade surfaces

I've created a library for creating and using b-spline surfaces in Python, utilizing parallel scipy.interpolate.RectBivariateSpline() instances to hold the knot vectors, (X, Y, Z) control point mesh, and degrees in u and v (the (t, c, k) tuple against which surface evaluation is performed). I also wrote a STEP parser to read surface data exported from CAD packages; I take the (t, c, k) values from the b_spline_surface_with_knots entities in the file and stuff them into my own objects. The surface library works pretty well for me, but the STEP parser is a pain and fails in various ways almost every time I use it. So I've tried using a 'real' STEP parser, like this:
from OCC.STEPControl import STEPControl_Reader
from OCC.IFSelect import IFSelect_RetDone, IFSelect_ItemsByEntity
step_reader = STEPControl_Reader()
status = step_reader.ReadFile('c:/LPT/nomdata/lpt3.stp')
if status == IFSelect_RetDone: # check status
failsonly = False
step_reader.PrintCheckLoad(failsonly, IFSelect_ItemsByEntity)
step_reader.PrintCheckTransfer(failsonly, IFSelect_ItemsByEntity)
ok = step_reader.TransferRoot(1)
_nbs = step_reader.NbShapes()
aResShape = step_reader.Shape(1)
else:
print("Error: can't read file.")
sys.exit(0)
Now I have this aResShape object, but no amount of poking and prodding it in IPython (nor googling) reveals how to get at the (t, c, k) values that define the surface.
Can someone please point me to the method that will reveal these values? Or is there possibly another Python-based STEP parser that's a little less opaque?
The question is a bit old, but just in case anybody else hits here with a similar problem...
The result of step_reader.Shape() is a TopoDS_Shape, which is a topological entity which can be divided into the following component topologies:
Vertex – a zero-dimensional shape corresponding to a point in geometry;
Edge – a shape corresponding to a curve, and bound by a vertex at each extremity;
Wire – a sequence of edges connected by their vertices;
Face – part of a plane (in 2D geometry) or a surface (in 3D geometry) bounded by a closed wire;
Shell – a collection of faces connected by some edges of their wire boundaries;
Solid – a part of 3D space bound by a shell;
Compound solid – a collection of solids.
Tipically, you'd query it with the method TopoDS_Shape::ShapeType() in order know what is that shape (vertex? edge?, ...).
If the model is formed by a single b-spline surface, the shape then should be a TopoDS_Face, that you can get by calling:
face = aResShape.Face();
Once you have the TopoDS_Face at hand, you can get the underlying geometry (Geom_Surface) like this:
surface = BRepAdaptor_Surface(face).Surface().BSpline();
Now that you have had access to the underlying geometry, you can call this object's methods and they will provide you with the information you need.
They are documented here:
https://www.opencascade.com/doc/occt-7.1.0/refman/html/class_geom___b_spline_surface.html
OpenCASCADE documentation may seem confusing, but I think you might be interested on this topic:
https://www.opencascade.com/doc/occt-7.0.0/overview/html/occt_user_guides__modeling_data.html#occt_modat_3
Hope it helps.

Modifying variables in Python function is affecting variables with different names outside the function

I have a nested dictionary containing a bunch of data on a number of different objects (where I mean object in the non-programming sense of the word). The format of the dictionary is allData[i][someDataType], where i is a number designation of the object that I have data on, and someDataType is a specific data array associated with the object in question.
Now, I have a function that I have defined that requires a particular data array for a calculation to be performed for each object. The data array is called cleanFDF. So I feed this to my function, along with a bunch of other things it requires to work. I call it like this:
rm.analyze4complexity(allData[i]['cleanFDF'], other data, other data, other data)
Inside the function itself, I straight away re-assign the cleanFDF data to another variable name, namely clFDF. I.e. The end result is:
clFDF = allData[i]['cleanFDF']
I then have to zero out all of the data that lies below a certain threshold, as such:
clFDF[ clFDF < threshold ] = 0
OK - the function works as it is supposed to. But now when I try to plot the original cleanFDF data back in the main script, the entries that got zeroed out in clFDF are also zeroed out in allData[i]['cleanFDF']. WTF? Obviously something is happening here that I do not understand.
To make matters even weirder (from my point of view), I've tried to do a bodgy kludge to get around this by 'saving' the array to another variable before calling the function. I.e. I do
saveFDF = allData[i]['cleanFDF']
then run the function, then update the cleanFDF entry with the 'saved' data:
allData[i].update( {'cleanFDF':saveFDF} )
but somehow, simply by performing clFDF[ clFDF < threshold ] = 0 within the function modifies clFDF, saveFDF and allData[i]['cleanFDF'] in the main friggin' script, zeroing out all the entires at the same array indexes! It is like they are all associated global variables somehow, but I've made no such declarations anywhere...
I am a hopeless Python newbie, so no doubt I'm not understanding something about how it works. Any help would be greatly appreciated!
You are passing the value at allData[i]['cleanFDF'] by reference (decent explanation at https://stackoverflow.com/a/430958/337678). Any changes made to it will be made to the object it refers to, which is still the same object as the original, just assigned to a different variable.
Making a deep copy of the data will likely fix your issue (Python has a deepcopy library that should do the trick ;)).
Everything is a reference in Python.
def function(y):
y.append('yes')
return y
example = list()
function(example)
print(example)
it would return ['yes'] even though i am not directly changing the variable 'example'.
See Why does list.append evaluate to false?, Python append() vs. + operator on lists, why do these give different results?, Python lists append return value.

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