Callbacks in Python Maya plugin - python

I am trying to create a callback in my Maya plugin that runs every time a new object is created. The callback works but the data object passed is of NoneType. I want to access the newly created object in my callback function, how do i do that?
g_Callbacks = list()
def initializePlugin( obj ):
g_Callbacks.append( OpenMaya.MEventMessage.addEventCallback( "DagObjectCreated", callback ) )
...
def callback( data ):
print data.apiTypeStr()

From the comments it sounds like there are two separate issues.
Notificiations on object creation are 'free', in the sense that you can do this without the API by using a scriptJob. Here's a trivial example
def new_object_callback():
print "created", cmds.ls(sl=True)
cmds.scriptJob(e=('DagObjectCreated', new_object_callback))
The callback here is just a plain python script , but it could be a function created by an MPXCommand (that link is a good intro to a very simple command plugin, btw).
There is one limitation here: the creation callback will fire once per undo block. A menu item, button or script creates a single undo --- which means that an action which creates multiple objects will only get that notification once (and the example code above will only print out the message for the last created object).
Another way of reading the question is how to fire a callback when you create the object yourself in a plugin. That's a simpler problem, although plugins for creating objects are kind of wordy to write. (A decent intro here -- the python stuff is interleaved with C++, because the docs for all of this are still written for C++) Every plugin node class has to have a function called initialize, which will be called every time the plugin creates a new node, so you can use that to call any code you need to run at startup.
OpenMaya is a real pain in the butt, since you're basically writing C++ through Python. Here's a couple of decent references:
http://www.amazon.com/Maya-Python-Games-Film-Reference/dp/0123785782
http://www.amazon.com/Practical-Programming-Python-Robert-Galanakis/dp/1849694729

Related

How to properly finalize/cleanup pythonnet CLR objects?

I'm using python to communicate with a certain piece of software through the provided API. As the API-code is written in C#, I use pythonnet to import the DLL and subsequently use it. It is beneficial to do so using e.g. Jupyter Lab or Jupyter Notebook when optimizing the code, as you can easily compare the results within your code and within the software. However, I run into an issue with cleanup. The API requires you to establish connection by running the following code
import clr
clr.AddReference('API')
api = __import__('API', globals(), locals(), [], 0)
connection = api.connection()
app = connection.connect()
Now you can communicate with the software using app. The main reason for my issue it that you are only allowed to have one app in the CLR. If you want to create a new one, you should call app.close() and subsequently newapp = connection.connect(). What happens when you create newapp without calling app.close() is not clearly defined. I'm unsure how C# would handle this, would it overwrite the app in memory, would app now also point to newapp, or something else? With that, I'm even more unsure how python+CLR handles it.
To ensure that your would always work with a properly connected app, I have created a class that only allows one instance of app to be present. This restriction is implemented by evaluating connection.Alive through the API, which is True when an app is spawned and not properly closed yet. The class is similar to:
class APIWrapper:
def __init__(self, api):
self.API = api
self.Connection = api.connection()
def connect():
if self.Connection.Alive:
raise RunTimeError('Only one live application is allowed at runtime')
app = self.Connection.connect()
return app
While this works fine, my issue arises when I accidentally do something like:
wrap = APIWrapper()
wrap.connect()
When doing so, the app goes live and wrap.Connection.Alive evaluates to True. However, as I do not assign the return of wrap.connect() to a variable, I cannot close it using app.close(). For example, if I do:
wrap = APIWrapper()
print(wrap.Connection.Alive) # -> False
app = wrap.connect()
print(wrap.Connection.Alive) # -> True
app.close()
print(wrap.Connection.Alive) # -> False
wrap.connect()
print(wrap.Connection.Alive) # -> True
I cannot close the connection anymore. I have thought about altering the class so the to just bind wrap.connect() to wrap.App and allow access through the attribute. This would solve the problem of losing the app, but I prefer to not have to call wrap.App continuously for code readability. Additionally, I am just wondering if there is a proper way of handling these finalization problems?
First of all, if calling wrap.connect() without storing the return value anywhere is the problem, then there is an easy solution for it: don't do it! It looks like the connection is a resource, so you must keep track of it to release it properly when the time comes.
In your example, what should happen to the previously created connection, when somebody calls connect() again?
Second, in Python there are two ways to explicitly keep track of resources:
with statements + context managers (highly recommended). In this case you would need to implement a context manager on your wrappers.
__del__ function, that you can define, that will be called when the object is no longer needed. This one you should avoid, because it will execute at arbitrary time, meaning when you try to create a new connection, the old one might still be around, because Python did not realize it should call __del__ yet.
Another alternative is to make a singleton.

bokeh multiple live streaming graphs in different objects / register update routine located in other class

I use python and bokeh to implement streamed live graphing. I want to include several live graphs into a gridplot and run into a kind of "deathlock".
The graphs (there are a lot of them) are created by different classes and the figure objects are returned and then used as input to the gridplot() function.
For live graphing curdoc().add_periodic_callback(update1, 300) references the update routine. I call the update routines of the other graphs directly from update1(). This works but gives me following error continuously:
`raise RuntimeError("_pending_writes should be non-None when we have a document lock, and we should have the lock when the document changes")
This is expected behavior since data of the other graphs is altered from the 'outside' of their object and from an 'unregistered update routine'. I want to get rid of this error.
In my main object (where the layout is pieced together and curdoc().add_root() is called) I intend to register the other graphs update routines (which have to be regular object routines, so that they can be referenced.) via curdoc().add_periodic_callback(). the problem with this approach is, that the objects update functions take the self parameter and bokeh does not accept that.
Yet I can not do it without self, cause update() needs to reference the source.stream object.
I have no clue how to solve this or do it the 'correct' way. Suggestions are appreciated.
thanks
for clarification:
main object:
def graph(self):
.... bokeh code
#count()
def update(t):
.... update code
curdoc().add_root(gridplot([[l[0]], [l[1]]], toolbar_location="left", plot_width=1000))
curdoc().add_periodic_callback(update, 300)
this works
generic other object
def graph(self):
.... bokeh code
def update(self,t): ....
main object:
curdoc().add_periodic_callback(other_object.update, 300)
this does NOT work.
"_pending_writes should be non-None when we have a document lock, and we should have the lock when the document changes"
Disclaimer: I've been dealing with this error in my own work for two weeks now, and finally resolved the issue today. (: It's easy when every sample you see comes with a csv file that's read and pushed to the doc, all in the same thread, but when things get real and you have a streaming client doing the same thing, suddenly everything stops working.
The general issue is, Bokeh server needs to keep its version of the document model in sync with what Bokeh client has. This happens through a series of events and communication happening between the client (Javascript running in the browser) and the server (inside an event loop that we will get to later on).
So every time you need to change the document, which essentially affects the model, the document needs to be locked (the simplest reason I could think of is concurrency). The simplest way to get around this issue, is to tell the Bokeh document instance you hold, that you are in need of making a change - and request a callback, so Bokeh manages when to call your delegate and allow you to update the document.
Now, with that said, there are few methods in bokeh.plotting.Document that help you request a callback.
The method you would want to probably use based on your use case, for example, if you need an ASAP callback, is add_next_tick_callback.
One thing to remember is that, the reference/pointer to your doc must be correct.
In order to make sure of that, I did wrap all my charting application into a class, and kept an instance of doc internally to access its add_next_tick_callback when new data is received. The way I could point at the right instance, was to initialize my Bokeh app using bokeh.server.server.Server - when it initializes the app, you will receive a doc variable that it's created before starting the server - that would be the right reference to the doc you present in the app. One benefit for having this "chart initializer" in a class, is that you can instantiate it as many times as you may need to construct more charts/documents.
Now, if you are a fan of data pipelines, and streaming, and use something like StreamZ to stream the data to the Pipe or Buffer instance you may have, you must remember one thing:
Be aware of what happens asynchronously, in a thread, or outside of it. Bokeh relies on tornado.ioloop.IOLoop for the most part, and if you are anywhere near running things asynchronously, you must have come across asyncio.
The event loops on these two modules can conflict, and will affect how/when you can change the document.
If you running your streaming in a thread (as the streaming client I wrote did..), make sure that thread has a current loop, otherwise you will face other similar issues. Threads can cause conflicts with internally created loops, and affect how they interact with each other.
With something like the following:
asyncio.set_event_loop(asyncio.new_event_loop())
Finally, be aware of what #gen.coroutine does in tornado. Your callbacks for the streaming, the way I understood, must be decorated with #gen.coroutine if you are doing things asynchronously.

How can I initialise an object to be used in multiple calls Python from the command-line

I have a script I've written that uses a very large object. I load the object with pickle, but it takes quite a few seconds to do so. That's not a big deal if it has to happen once or twice, but I'm hoping to use the code many hundreds or thousands of times!
I think my issue is that I'd like to almost 'leave' the object alive and then be able to call it from command line whenever I need it. I'm reasonably new to Python so I'm not sure how possible that is; sorry if I haven't used the right terminology in my question. I'm writing and running my python in Spyder at the moment, but eventually I'd like to run it on a server, calling the code as and when required.
If your script is looping over the python program, move the loop inside the program.
If on the other hand, you want to be able to use the large object on demand, you probably need a client/server configuration. Thriftpy is a very simple way to achieve this. The thriftpy server will hold the object and the processing logic, and the client will be a command line script that will call the server and pass whatever parameters you need to process the object.

Save Python variables with Maya scene

How can I save Python variables in a Maya scene?
For example I create a variable x in Maya's Script Editor and I save the scene. When I reopen Maya and reopen the saved scene, x variable doesn't exist anymore.
Is it possible?
Yes, use a script node, use a optionVar or store the variable in a attribute.
In general
Scene persistence is supposed to be built with nodes. In fact everything in Maya is supposed to be a node or built out of a node tree. The bonus is if you make your computation a node then maya will automatically handle all the stuff for you. This is how render engines for example store the data. They register a special node and read the data from the node.
Needing to ask this question, "How can I save Python variables in a Maya scene?", is a indication that you indeed should have built a nodal solution. There are a few exceptions to this and those are related to general user GUI preferences that should be saved as optionVars.
Maya does not actually enforce that you do things sanely. You are free to do whatever you want, sane or not. So it is possible, tough a bit fishy, to use a scene save scriptjob to store the snapshot of your environment to a script node that auto runs on load. This is undesirable in the same way as using global variables is undesirable for code in general. It is also slightly unreliable, as a user can disable auto running of on load scriptNodes, for good reason.
About nodal solutions
Maya as a rule does not work the way most people intuitively expect at first glance. Just like the text you write in code is not what your computer generally executes, but rather the compiled code is what gets executed. The compiler has a bit of leeway on what i can do in this context and usually throws away some stuff. So in code its not really sane to think in terms of what the text looks like in code but rather what structures it builds.
In Maya the structures you should be building are nodes. There are two extra use cases outside this and those are:
Exporters (Importers but due to their nature they are node bound because they target Maya)
Graphical user interfaces that introspect Mayas current state or bring external info to the user
These two could only specialize in reading nodes. Mayas object auxiliary function nature can quite efficiently hide the fact from the user but this is essentially want your doing. Anything outside this scope does not benefit form using Maya. So whenever you use maya you want to capitalize on nodes. Think of it as if you are using a second language on top of your own programming language. This one is the actual language of Maya.
Let us start with a naive approach for randomizing point positions for a mesh (note i avoid the term object as Maya has no such concept):
import maya.cmds as cmds
import random
def randomize_points(scale):
sel = cmds.polyListComponentConversion(ff=1, fe=1, fuv=1, fvf=1, tv=1)
sel = cmds.ls(sel ,fl=1)
for item in sel:
cmds.move((random.random()-0.5)*scale,
(random.random()-0.5)*scale,
(random.random()-0.5)*scale,
item, r=1)
It works, but has three deficiencies:
You can not know what it looks like until you run it.
You can undo tough so test and undo. Maybe youd need a seed variable for the random too?
You need to build a GUI for this so the user can work it.
There's is no way to specify the profile of the randomness.
(This one is here so it easy for you to run the code i could use one of many pythons noise implementations. I will fix this nonetheless.)
The code is straightforward in sense id does what a user would do in the GUI. But what really happens? Where do the changes go? Simply put the answer is 2 fold they go either to the tweak array (most likely), or the objects position array. But there is a better way i can use Maya primitives to manipulate a similar behavior. Enter nodes.
First you need to go a bit node shopping, what nodes could actually provide a noise functionality? The thing is there does not seem to be many contenders here. There is despite this one node that's basically roll your own deformation and that is the particle node. So this is how you'd attack the problem with a particle node, some noise and a few attributes:
... to be continued ...
Here is the simple example to saving a python variable inside maya file.
lets Save Variable X in Maya file and the value of X is 100.
and we dont want see this error:
# Error: NameError: name 'x' is not defined #
To Save Variable in Maya file Execute this code before you save your Maya file.
import maya.cmds as cmds
var_node = cmds.scriptNode(scriptType=1, name='CustomVariable', beforeScript='python("x = 100")')
cmds.scriptNode( var_node, executeBefore=True )
open file again and execute:
print x

Issue while trying to copy pyside object

I am having a rather frustrating problem using pyside and I would welcome any advice.
First, some context
I have created a simple GUI using Qt Designer and I have used pyside-uic.exe onto my .ui file in order to generate the associated Python file.
I am using Python 3.3 and pyside 1.2.1 with Qt Designer 4 (Qt 4.8.5).
I am using the following code to launch my GUI:
class my_dialog(QMainWindow, my_gui.Ui_main_window):
def __init__(self, parent=None):
super(my_dialog, self).__init__(parent)
self.setupUi(self)
if ("__main__" == name):
app = QApplication(sys.argv)
main_dialog = my_dialog()
# (1)
main_dialog.show()
sys.exit(app.exec_())
What I would like to achieve
My GUI features several tabs. The number of tabs is not pre-determined and is evaluated at run time. As a result, I've decided to create one tab in Qt Designer, to use as a template.
The first time I need to add a tab, I modify this template, and if I need any additionnal tab, I was planning on making a copy of that tab and then modify that copy appropriately.
The issue I have encountered
My problem is that I can't seem to find a way to copy the tab widget. After some research, I thought the copy module (or the pickle module, see edit) might do the trick (the following code was inserted at (1)):
new_tab = copy.deepcopy(main_dialog.my_tab)
main_dialog.my_tabs.addTab(new_tab, "")
But that triggered the following error:
main_dialog.my_tabs.addTab(new_tab, "")
RuntimeError: Internal C++ object (Pyside.QtGui.QWidget) already deleted
What I could find on my own
I have seen on SO and other sites that there may be issues, when using pyside, of objects being collected because there is no reference to them in Python.
The fact remains, however, that even if I move this code to very setupUi() method in the .py file generated by pyside, I still get the exact same error.
It is also worth noting that I am able to access the my_tab object to modify its content without any trouble.
I am able to create another tab from scratch in my code and main_dialog.my_tabs.addTab(new_tab, "") works perfectly fine in that context.
After some experimentations, I realized the problem probably occurs at the copy of the my_tab object. Indeed, copying a tab object that I just created, I could see that trying to add the copy to the GUI tabs failed too, and with the same error.
It looks like the copy fails somehow, or the object is being immediately deleted for some reason. That's what I'm infering anyway...
My question
Considering all this, I would like to find a way to either succeed in the object copy, find another way to use an existing pyside object as template for other similar objects.
I could of course take the code for the tab out of the generated file and code my own addTab() method. However, I am expected to build from an existing .ui file and avoid hardcoding GUI elements.
EDIT:
When using pickle:
new_tab = pickle.loads(pickle.dumps(main_dialog.my_tab, -1))
I get the following error:
new_tab = pickle.loads(pickle.dumps(main_dialog.my_tab, -1))
_pickle.PicklingError: Can't pickle <class 'Pyside.QtCore.SignalInstance'>: attribute lookup Pyside.QtCore.SignalInstance failed.
The suggestion of creating a separate ui file for the widget you want to copy seems a reasonable solution. Although it would seem that generating a separate gui module for the widget using pyside-uic would work just as well as using QUiLoader (in fact, it would be slightly more efficient).
As for the question of why cloning widget using e.g. copy.deepcopy doesn't work: this is because it will only copy the python wrapper, and not the underlying C++ object. A somewhat fuller explanation can be found in this answer.
After some more research, I believe copying a pyside object using one of those techniques is not possible.
The first thing to note is that there is no built-in function to clone a Qt widget, so the cloning should be done using modules like copy, pickle or marshal.
Using pickle or marshal fails because the object is found to be not pickable.
Whilst the copy.copy or copy.deeepcopy do not raise any warning/exception/error, the copy does not occur, or is deleted right afterwards for some reason.
When trying to pass in the deepcopy as parameter to addTab, no warning/exception/error is thrown, yet the program stops at that line and exits back to the Python command prompt. The fact that it takes a few seconds on that line before exiting makes me assume deepcopy tries to browse through the object attributes and fails at some point. Doing the same with copy results in the previous C++ object deleted error mentionned in the question, so I can only infer the deepcopy operation does fail.
As a result, the only advice I could give someone looking for a similar answer is to implement their own copy-widget function, which is ultimately what I will do now.
Still, I wish to understand how is that deepcopy fails like this, so silently, yet provoking the end of the execution. I started a thread to try and find an answer to this there
EDIT:
I found a solution for this problem, that respects my requirements of not hard-coding GUI elements and of using Qt Designer to create the GUI and templates for repeatable elements. I hope it helps anyone having the same issue:
The idea is that it is possible using Qt -- and pyside -- to load a given .ui file at run time, using the QUiLoader() method. It is thus possible to parse the .ui file to extract a given widget (.ui files are simple XML files) and use the following code to use it:
loader = QUiLoader()
ui_file = QFile("path_to_ui_file.ui")
ui_file.open(QFile.ReadOnly)
new_tab = loader.load(ui_file)
ui_file.close()
main_dialog.my_tabs.addTab(new_tab, "")
And it works!
A few things about the above example:
the second line assumes you've isolated your widget in the file path_to_ui_file.ui
in my example the widget is a tab, of course it works with any widget you might have done, the last line is only provided to show that the error is no longer thrown
finally, this approach has the pro of allowing you to use tools like Qt Designer to develop your GUI elements even when some variables are involved, such as just how many of that tabs do you want?

Categories

Resources