Skip to content
Marcus Ottosson edited this page Feb 15, 2015 · 7 revisions

This page will walk you through the theory and implementation of Pyblish Endpoint - a RESTful interface towards externally running front-ends of Pyblish - along with an example of how it is used in the current Pyblish Maya Integration.


Introduction

The current GUI, Pyblish QML, runs as an external process to Maya. Because it is external, we can't get direct access to it via things like Qt signals and slots. Instead, we must tell Maya to "listen" for requests coming from the GUI. This listener is called Pyblish Endpoint.

Ears And Mouths

Similar to how people have both ears and mouths, Endpoint provides Maya with both which means that it and our GUI can start to have have meaningful conversations. Much like they would automatically have if they were native Python scripts.

Endpoint puts an ear and a mouth on Maya, and without it our GUI could speak to it, but Maya refuse to neither hear nor talk (like an old married couple), so for the GUI to work, we'll need Endpoint.

import pyblish_endpoint
pyblish_endpoint.server.start_production_server(...)

Endpoint asks for two things; a Service and a port.

The port between Endpoint and QML must match, as it is at this number - or "frequency" - that communication will take place.

The Service on the other hand is an abstraction of everything Endpoint is able to hear and say, and therefore everything that Maya and our GUI can possibly say to each other.

A Small Vocabulary

You can think of it as their entire vocabulary. Or, as the inputs and ouputs of a Node in Maya, Nuke or Houdini.

 _________      _________      _________ 
|         |    |         |    |         |
|         |    |         |    |         |
|  Host   o----o Service o----o   GUI   |
|         o----o         o----o         |
|         o----o         o----o         |
|_________|    |_________|    |_________|

In which Maya, or whatever the host, plugs into one side, and the GUI plugs in to the other.

To satisfy this need, we'll need to:

import pyblish_endpoint
import pyblish_endpoint.service

service = pyblish_endpoint.service.EndpointService
pyblish_endpoint.server.start_production_server(12345, service)

To Walk And Chew Gum

However, because we would like to be able to "walk and chew gum at the same time", we can't simply start listening and forget about all other troubles. This would block the host.

To work around this, we tell it to spend its time listening in a separate thread.

def server():
    pyblish_endpoint.server.start_production_server(12345, service)

worker = threading.Thread(target=server)
worker.deamon = True  # Kill thread when parent thread is killed
worker.start()

Don't Be A Stranger

The final piece of the puzzle is to customise the vocabulary, or Service.

Because some hosts have particular requirements when it comes to being told what to do from a separate thread, these host must modify the vocabulary such that it conforms to whatever is required of it.

In the case of Maya and Nuke, neither of them likes having some foreign thread tell them what to do, we need to change the vocabulary of Endpoint to respect this.

# This..

class EndpointService(object):
    def process(self, *args, **kwargs):
        ...

# Becomes this..

class MayaService(pyblish_endpoint.service.EndpointService):
    def process(self, *args, **kwargs):
        # Do whatever it is that you do, but do first pass
        # it through this utility function.
        return utils.executeInMainThreadWithResult(
            super(MayaService, self).process, *args, **kwargs)

Conclusion

Here is a complete example of how all of this ties together for the integration in Maya.

import os
import threading
import pyblish_endpoint
import pyblish_endpoint.service
import pyblish_maya.version

from maya import utils

class MayaService(pyblish_endpoint.service.EndpointService):
    def init(self, *args, **kwargs):
        orig = super(MayaService, self).init
        return wrapper(orig, *args, **kwargs)

    def next(self, *args, **kwargs):
        orig = super(MayaService, self).next
        return wrapper(orig, *args, **kwargs)

    def versions(self):
        versions = super(MayaService, self).versions()
        versions["pyblish-nuke"] = version
        return versions

def server():
    pyblish_endpoint.server.start_production_server(12345, MayaService)

worker = threading.Thread(target=server)
worker.deamon = True  # Kill thread when parent thread is killed
worker.start()

# Whichever port you choose to listen on, pass this information
# along to an environment variable that a GUI can then ask for
os.environ["ENDPOINT_PORT"] = 12345

print "pyblish: Endpoint running @ %i" % 12345