Developing with IPC
The workflow for implementing a new feature into the user interface generally goes like this.
- Expose a feature of Pyblish through pyblish-rpc
- Make use of the exposed feature through pyblish-qml
In this article we will have a look at how to do just that.
Overview
As we saw in A primer on IPC, to be able to use functionality in one process from another process, we register a function with the RPC server.
Aside from registering a plain function, we are also able to register an instance of a class, called "service". We use it in place of freestanding functions in order to share memory state amongst one or more calls, such as maintaining reference to discovered plug-ins and instances.
The default service is located in pyblish-rpc/service.py and looks like this.
class RpcService(object):
def __init__(self):
self._context = None
self._plugins = None
self._provider = None
self.reset()
def ping(self):
"""Used to check connectivity"""
return {
"message": "Hello, whomever you are"
}
This service is registered by default when launching any integrated host. If you have Pyblish installed, try launching any host and type this in.
import xmlrpclib
proxy = xmlrpclib.ServerProxy("http://127.0.0.1:9001/pyblish")
proxy.ping()
# {"message": "Hello, whomever you are"}
Note the additional path
/pyblish
.
You can find the exposed port number of your host via the environment variable PYBLISH_CLIENT_PORT
which in this case is 9001
. If the variable isn't available, make sure the Pyblish has been installed correctly and that the integration is loaded.
Exposing a feature for RPC
Now that we know how features are exposed, let's try exposing something we can use with pyblish-qml.
class RpcService(object):
def add_two_numbers(self, a, b):
return a + b
With the method added, let's have a look at how we can use this in pyblish-qml.
Using a feature
Before diving into the QML side, let's stick to a standalone Python interpreter.
QML instantiates Proxy
from pyblish-rpc/client.py and uses it for any and all communication with Pyblish within a host. Let's try doing that in a plain Python interpreter.
from pyblish_rpc.client import Proxy
proxy = Proxy(9001)
The port is normally passed to QML from the host during the point at which a user triggers its visibility; e.g. by pressing "Publish" from the file-menu or by calling .show()
from it's integration, such as pyblish_maya.show()
.
For this example, we will assume a host is running at 9001
.
Now we can call any function exposed via this service.
# Retrieve a list of plugins
plugins = proxy.discover()
# Retrieve the current context
context = proxy.context()
Including the one we just implemented.
print(proxy.add_two_numbers(1, 2))
# 3
Using a feature in the controller
In a typical model/view/controller application, the controller is the part where logic and event handling occurs. This is where we will make use of our feature.
Open up pyblish-qml/control.py and add the calling method to the Controller
class.
# pyblish-qml/pyblish_qml/control.py
class Controller(QtCore.QObject):
...
def add_two_numbers(self, a, b):
result = self.host.add_two_numbers(a, b)
print("The result is: %s" % result)
return result
...
self.host
represents the currently active host and is an instance of the Proxy
class we saw above.
Whenever a host connects/shows the GUI, Proxy
is reinstantiated to reflect the change.
Using a feature in QML
To call upon a feature from QML directly, all we need to do is decorate the method we just made.
# pyblish-qml/pyblish_qml/control.py
class Controller(QtCore.QObject):
...
@QtCore.pyqtSlot(int, int, result=int)
def add_two_numbers(self, a, b):
result = self.host.add_two_numbers(a, b)
print("The result is: %s" % result)
return result
...
We are then able to call this function from within QML.
// pyblish-qml/pyblish_qml/qml/Overview.qml
Footer {
id: footer
mode: overview.state == "publishing" ? 1 : overview.state == "finished" ? 2 : 0
width: parent.width
anchors.bottom: parent.bottom
onReset: {
// Run our function at the touch of the reset-button
app.add_two_numbers(2, 4)
app.reset()
}
}
Notice how the controller is exposed as a global variable in QML called app
. Try launching the GUI and pressing the Reset button, and keep an eye out in the output.
The result is: 6