Publishing using pyblish

As an example we will look at the Maya geometry asset which uses pyblish for publishing. Note that the concepts described in this article is generic and can be applied to Nuke and other applications.

Let us have a look at the structure of the geometry asset plugin:

ftrack_connect_maya_publish/asset/geometry/
    pyblish_plugins/
        __init__.py
        collect.py
        extract_alembic.py
        extract_mayabinary.py
    __init__.py
    geometry_asset.py

In the pyblish_plugins directory there are a number of pyblish plugins defined. These takes care of the publish process: collecting what to publish, validating and extracting the data, then integrating (registering) everything in ftrack.

In the geometry_asset.py we define the asset plugin, how to publish, import and switch to a different version for an already imported asset. The asset plugin does not necessarily have to publish using pyblish.

However, the asset plugin itself not registered here. Instead let us look at register_geometry_asset.py in the resource/ directory:

# :coding: utf-8
# :copyright: Copyright (c) 2016 ftrack

import functools

import ftrack_api
import ftrack_connect_pipeline.asset

from ftrack_connect_maya_publish.asset.geometry import geometry_asset


def create_asset_publish():
    '''Return asset publisher.'''
    return geometry_asset.PublishGeometry(
        description='publish geometry to ftrack.',
        asset_type_short='geo'
    )


def register_asset_plugin(session, event):
    '''Register asset plugin.'''
    geometry = ftrack_connect_pipeline.asset.Asset(
        identifier='geo',
        label='Geometry',
        icon='http://www.clipartbest.com/cliparts/9cz/EzE/9czEzE8yi.png',
        create_asset_publish=create_asset_publish
    )
    geometry.register(session)


def register(session):
    '''Subscribe to *session*.'''
    if not isinstance(session, ftrack_api.Session):
        return

    session.event_hub.subscribe(
        'topic=ftrack.pipeline.register-assets',
        functools.partial(register_asset_plugin, session)
    )

Here we find the registration of the asset plugin for geometry. The asset plugin will be registered with a unique identifier when the application’s ftrack-python-api session emits the ftrack.pipeline.register-assets. The create_asset_publish argument, PublishGeometry instance will handle publishing.

Note

In future versions there will be a import_asset and a switch_asset argument.

Let us look at a definition of the PublishGeometry class that got imported:

# :coding: utf-8
# :copyright: Copyright (c) 2016 ftrack

import ftrack_connect_pipeline.asset

import maya.cmds as cmds


class PublishGeometry(ftrack_connect_pipeline.asset.PyblishAsset):
    '''Handle publish of maya geometry.'''

    def get_options(self):
        '''Return global options.'''
        options = [
            {
                'type': 'group',
                'label': 'Maya binary',
                'name': 'maya_binary',
                'options': [{
                    'name': 'reference',
                    'label': 'Reference',
                    'type': 'boolean',
                }, {
                    'name': 'history',
                    'label': 'History',
                    'type': 'boolean',
                }, {
                    'name': 'channels',
                    'label': 'Channels',
                    'type': 'boolean',
                    'value': True
                }, {
                    'name': 'expressions',
                    'label': 'Expressions',
                    'type': 'boolean',
                    'value': True
                }, {
                    'name': 'constraints',
                    'label': 'Constraints',
                    'type': 'boolean',
                    'value': True
                }, {
                    'name': 'shaders',
                    'label': 'Shaders',
                    'type': 'boolean',
                    'value': True
                }]
            },
            {
                'type': 'group',
                'label': 'Alembic',
                'name': 'alembic',
                'options': [{
                    'name': 'include_animation',
                    'label': 'Include animation',
                    'type': 'boolean',
                    'value': True
                }, {
                    'name': 'uv_write',
                    'label': 'UV write',
                    'type': 'boolean',
                    'value': True
                }, {
                    'name': 'world_space',
                    'label': 'World space',
                    'type': 'boolean',
                    'value': True
                }, {
                    'name': 'write_visibility',
                    'label': 'Write visibility',
                    'type': 'boolean',
                    'value': True
                }]
            }
        ]

        default_options = super(PublishGeometry, self).get_options()

        return default_options + options

    def get_publish_items(self):
        '''Return list of items that can be published.'''
        match = set(['geometry', 'ftrack'])

        options = []
        for instance in self.pyblish_context:
            if match.issubset(instance.data['families']):
                options.append(
                    {
                        'label': instance.name,
                        'name': instance.name,
                        'value': instance.data.get('publish', False)
                    }
                )

        return options

    def get_item_options(self, name):
        '''Return options for publishable item with *name*.'''
        return []

    def get_scene_selection(self):
        '''Return a list of names for scene selection.'''
        return cmds.ls(assemblies=True, long=True, sl=1)

Here we see that it inherits ftrack_connect_pipeline.asset.PyblishAsset and will, as the name suggests be using pyblish for the publishing process.

While processing pyblish plugins will be accessing options from the interface. These options are defined in the methods on the PublishGeometry class and and are following the syntax described in Developing actions user interface.

There are two additional types only available in the new tools:

group

Visually group a number of options in the UI. Options will be saved under the name key:

{
    'type': 'group',
    'label': 'Maya binary',
    'name': 'maya_binary',
    'options': [..]
}
qt_widget

The qt_widget type can be used to present a custom widget to the user:

{
    'widget': StartEndFrameField(start_frame=0, end_frame=1001),
    'name': 'frame_range',
    'type': 'qt_widget'
}

The options will be saved under the name key. The StartEndFrameField being a subclass of ftrack_connect_pipeline.ui.widget.field.base.BaseField. The widget must:

  1. Implement the value method that returns the current value of the field.

  2. Emit value_changed signal with value when the underlying value changes.

Here is an example of a start/end frame qt based widget:

import sys

from ftrack_connect_pipeline.ui.widget.field.base import BaseField

from QtExt import QtWidgets


class StartEndFrameField(BaseField):
    '''Start and end frame fields.'''

    def __init__(self, start_frame, end_frame):
        '''Instantiate start and end frame field.'''
        super(StartEndFrameField, self).__init__()
        self.setLayout(QtWidgets.QHBoxLayout())

        self.start_frame = QtWidgets.QDoubleSpinBox()
        self.start_frame.setValue(start_frame)
        self.start_frame.setMaximum(sys.maxint)
        self.start_frame.setDecimals(0)
        self.start_frame.valueChanged.connect(self.notify_changed)

        self.end_frame = QtWidgets.QDoubleSpinBox()
        self.end_frame.setValue(end_frame)
        self.end_frame.setMaximum(sys.maxint)
        self.end_frame.setDecimals(0)
        self.end_frame.valueChanged.connect(self.notify_changed)

        self.layout().addWidget(QtWidgets.QLabel('Frame Range'))
        self.layout().addWidget(self.start_frame)
        self.layout().addWidget(self.end_frame)

    def notify_changed(self, *args, **kwargs):
        '''Notify the world about the changes.'''
        self.value_changed.emit(self.value())

    def value(self):
        '''Return value.'''
        return {
            'start_frame': int(self.start_frame.value()),
            'end_frame': int(self.end_frame.value())
        }

Notable methods that are implemented on the PublishGeometry class:

get_publish_items

Return a list of items that can be published from the scene. In a pyblish based publish workflow this is ususally done by filtering on the pyblish instance family/families.

get_options

Return a list of options that are general options for the publish.

get_item_options

Return a list of options that are valid for the given item name.

These methods can access the current pyblish context on self:

for instance in self.pyblish_context:
    print instance

Pyblish plugins

In the pyblish_plugins directory we have the plugins that will collect the geometries from the scene and extract them as a maya binary and/or alembic.

Common pyblish plugins are defined in ftrack_connect_maya_publish/shared_pyblish_plugins and will be used for collection and integration plugins that are shared between asset plugins.