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.
Related
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()
I have written a script to avoid creating duplicates for vertex and edges but I have some troubles with edges.
This is the script:
g.V().has('dog', 'name', 'pluto').fold().\
coalesce(__.unfold(), __.addV('dog').property('name', 'pluto')).store('dog').\
V().has('person', 'name', 'sam').fold().\
coalesce(__.unfold(), __.addV('person').property('name', 'sam')).store('person').\
select('person').unfold().\
coalesce(__.outE('has_dog').where(__.inV().has(T.id, __.select('dog').unfold().id())),
__.addE('has_dog').to(__.select('person').unfold())).toList()
With this, I create the new two vertex and the new edge. If I execute it again, no new vertex and edge is created. For now all it's ok.
If I change 'pluto' with 'charlie' to create a new 'dog' vertex, this script creates the new vertex but return the previous edge created with 'pluto'. So if that 'person' already has that relationship, the script not creates a new one.
The thing I don't understand, it's that the code
__.select('dog').unfold().id()
should return the id of the new/old 'dog' vertex and check if the edge with 'person' exists.
If I execute that script to get the id and I replace that script with the id, for example
__.outE('has_dog').where(__.inV().has(T.id, 42))
the script works correctly and creates the edge with the new 'dog' vertex.
Why with the script to get id doesn't work but with the integer works? It's no sense because I should have the same result.
Thanks.
The issue you have run into is that a has step cannot take an arbitrary traversal but a where step can. You just need to reformulate the has step as a where step. An arbitrary traversal inside a has step is treated as "true" if it returns any result. This is one of those Gremlin things that looks as if it should work but actually does not.
Here is a contrived example that shows the where...by pattern that should be helpful in your case.
gremlin> g.V(3).as('x').V().where(eq('x')).by(id)
==>v[3]
As you are using store the where step becomes something like this
gremlin> g.V(3).store('x').V().where(eq('x')).by(id).by(unfold().id())
==>v[3]
So the line in your code will end up something like this
filter(__.inV().where(eq('dog')).by(T.id).by(unfold().id()))
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;
The python interface of igraph has a class called metamagic, serving the purpose to collect graphical parameters for plotting. I am writing a module using igraph, and I almost started to write my own wrapper functions for this purpose, when I've found metamagic in the documentation. But after searching and trying, it's still not clear how to use these classes. If I define an AttributeCollectorBase class for edges, like this:
class VisEdge(igraph.drawing.metamagic.AttributeCollectorBase):
width = 0.002
color = "#CCCCCC44"
Then, is there an easy way to pass all these parameters to the igraph.plot() function? Or I can only do one by one, like this: plot(graph,edge_color=VisEdge(graph.es).color)?
And what if I would like to use not constant parameters, but calculate by a custom function? For example, vertex_size proportional to degree. The func parameter of the AttributeSpecification class supposed to do this, isn't it? But I haven't seen any example how to use it. If I define an AttributeSpecification instance, like this:
ds = igraph.drawing.metamagic.AttributeSpecification(name="vertex_size",alt_name="size",default=2,func='degree')
After how to pass it to an AtributeCollector, and finally to plot()?
(To put things in context: I am the author of the Python interface of igraph).
I'm not sure whether the metamagic package is the right tool for you. The only purpose of the AttributeCollectorBase class is to allow the vertex and edge drawers in igraph (see the igraph.drawing.vertex and igraph.drawing.edge packages) to define what vertex and edge attributes they are able to treat as visual properties in a nice and concise manner (without me having to type too much). So, for instance, if you take a look at the DefaultVertexDrawer class in igraph.drawing.vertex, you can see that I construct a VisualVertexBuilder class by deriving it from AttributeCollectorBase as follows:
class VisualVertexBuilder(AttributeCollectorBase):
"""Collects some visual properties of a vertex for drawing"""
_kwds_prefix = "vertex_"
color = ("red", self.palette.get)
frame_color = ("black", self.palette.get)
frame_width = 1.0
...
Later on, when the DefaultVertexDrawer is being used in DefaultGraphDrawer, I simply construct a VisualVertexBuilder as follows:
vertex_builder = vertex_drawer.VisualVertexBuilder(graph.vs, kwds)
where graph.vs is the vertex sequence of the graph (so the vertex builder can get access to the vertex attributes) and kwds is the set of keyword arguments passed to plot(). The vertex_builder variable then allows me to retrieve the calculated, effective visual properties of vertex i by writing something like vertex_builder[i].color; here, it is the responsibility of the VisualVertexBuilder to determine the effective color by looking at the vertex and checking its color attribute as well as looking at the keyword arguments and checking whether it contains vertex_color.
The bottom line is that the AttributeCollectorBase class is likely to be useful to you only if you are implementing a custom graph, vertex or edge drawer and you want to specify which vertex attributes you wish to treat as visual properties. If you only want to plot a graph and derive the visual properties of that particular graph from some other data, then AttributeCollectorBase is of no use to you. For instance, if you want the size of the vertex be proportional to the degree, the preferred way to do it is either this:
sizes = rescale(graph.degree(), out_range=(0, 10))
plot(graph, vertex_size=sizes)
or this:
graph.vs["size"] = rescale(graph.degree(), out_range=(0, 10))
plot(g)
If you have many visual properties, the best way is probably to collect them into a dictionary first and then pass that dictionary to plot(); e.g.:
visual_props = dict(
vertex_size = rescale(graph.degree(), out_range=(0, 10)),
edge_width = rescale(graph.es["weight"], out_range=(0, 5), scale=log10)
)
plot(g, **visual_props)
Take a look at the documentation of the rescale function for more details. If you want to map some vertex property into the color of the vertex, you can still use rescale to map the property into the range 0-255, then round them to the nearest integer and use a palette when plotting:
palette = palettes["red-yellow-green"]
colors = [round(x) for x in rescale(g.degree(), out_range=(0, len(palette)-1))]
plot(g, vertex_color=colors, palette=palette)
Let's say I have a bpy.types.Object containing a bpy.types.Mesh data field; how can I apply one of the modifiers associated with the object, in order to obtain a NEW bpy.types.Mesh, possibly contained within a NEW bpy.types.Object, thus leaving the original scene unchaged?
I'm interested in applying the EdgeSplit modifier right before exporting vertex data to my custom format; the reason why I want to do this is to have Blender automatically and transparently duplicate the vertices shared by two faces with very different orientations.
I suppose you're using the 2.6 API.
bpy.ops.object.modifier_apply (modifier='EdgeSplit')
...applies to the currently active object its Edge Split modifier. Note that it's object.modifier_apply (...)
You can use
bpy.context.scene.objects.active = my_object
to set the active object. Note that it's objects.active.
Also, check the modifier_apply docs. Lot's of stuff you can only do with bpy.ops.*.
EDIT: Just saw you need a new (presumably temporary) mesh object. Just do
bpy.ops.object.duplicate()
after you set the active object and the new active object then becomes the duplicate (it retains any added modifier; if it was an object named 'Cube', it duplicates it, makes it active and names it 'Cube.001') to which you can then apply the modifier. Hope this was clear enough :)
EDIT: Note, that bpy.ops.object.duplicate() uses not active object, but selected. To ensure the correct object is selected and duplicated do this
bpy.ops.object.select_all(action = 'DESELECT')
object.select = True
There is another way, which seems better suited for custom exporters: Call the to_mesh method on the object you want to export. It gives you a copy of the object's mesh with all the modifiers applied. Use it like this:
mesh = your_object.to_mesh(scene = bpy.context.scene, apply_modifiers = True, settings = 'PREVIEW')
Then use the returned mesh to write any data you need into your custom format. The original object (including it's data) will stay unchanged and the returned mesh can be discarded after the export is finished.
Check the Blender Python API Docs for more info.
There is one possible issue with this method. I'm not sure you can use it to apply only one specific modifier, if you have more than one defined. It seems to apply all of them, so it might not be useful in your case.