# Copyright (c) 2017 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
import sgtk
HookBaseClass = sgtk.get_hook_baseclass()
[docs]class PublishPlugin(HookBaseClass):
"""
This class defines the required interface for a publish plugin. Publish
plugins are responsible for operating on items collected by the collector
plugin. Publish plugins define which items they will operate on as well as
the execution logic for each phase of the publish process.
"""
############################################################################
# Plugin properties
@property
def id(self):
"""
Unique string identifying this plugin.
"""
return self._id
@id.setter
def id(self, new_id):
"""
Allows to set the unique string identifying this plugin.
"""
self._id = new_id
@property
def icon(self):
"""
The path to an icon on disk that is representative of this plugin
(:class:`str`).
The icon will be displayed on the left side of the task driven by this
plugin, as shown in the image below.
.. image:: ./resources/task_icon.png
|
Icons can be stored within the same bundle as the plugin itself and
referenced relative to the disk location of the plugin, accessible via
:meth:`sgtk.Hook.disk_location`.
Example implementation:
.. code-block:: python
@property
def icon(self):
return os.path.join(
self.disk_location,
"icons",
"publish.png"
)
.. note:: Publish plugins drive the tasks that operate on publish items.
It can be helpful to think of items as "things" and tasks as the
"actions" that operate on those "things". A publish icon that
represents some type of action can help artists understand the
distinction between items and tasks in the interface.
"""
return None
@property
def name(self):
"""
The general name for this plugin (:class:`str`).
This value is not generally used for display. Instances of the plugin
are defined within the app's configuration and those instance names are
what is shown in the interface for the tasks.
"""
raise NotImplementedError
@property
def description(self):
"""
Verbose, multi-line description of what the plugin does (:class:`str`).
The string can contain html for formatting for display in the UI (any
html tags supported by Qt's rich text engine).
The description is displayed via the plugin's default
:meth:`create_settings_widget` implementation, as shown in the image
below:
.. image:: ./resources/plugin_description.png
|
A simple implementation example:
.. code-block:: python
@property
def description(self):
return '''
Creates a publish in Shotgun.
A <b>Publish</b> entry will be created in Shotgun which will
include a reference to the file's path on disk. Other users will
be able to access the published file via the
<b><a href='%s'>Loader</a></b> so long as they have access to
the file's location on disk.
''' % (loader_url,)
"""
raise NotImplementedError
@property
def settings(self):
"""
A :class:`dict` defining the configuration interface for this plugin.
The dictionary can include any number of settings required by the
plugin, and takes the form::
{
<setting_name>: {
"type": <type>,
"default": <default>,
"description": <description>
},
<setting_name>: {
"type": <type>,
"default": <default>,
"description": <description>
},
...
}
The keys in the dictionary represent the names of the settings. The
values are a dictionary comprised of 3 additional key/value pairs.
* ``type``: The type of the setting. This should correspond to one of
the data types that toolkit accepts for app and engine settings such
as ``hook``, ``template``, ``string``, etc.
* ``default``: The default value for the settings. This can be ``None``.
* ``description``: A description of the setting as a string.
Example implementation:
.. code-block:: python
@property
def settings(self):
return {
"Publish Template": {
"type": "template",
"default": None,
"description": "The output path template for this plugin."
},
"Resolution": {
"type": "str",
"default": "1920x1080"
"description": "The output resolution to export before publishing."
}
}
The settings are exposed via the ``settings`` key as the plugins are
configured via the ``publish_plugins`` setting in the app's
configuration. Example::
publish_plugins:
- name: Export and Publish
hook: "{config}/export_and_publish.py"
settings:
Publish Template: export_template
Resolution: 2048x1556
The values configured for the plugin will be supplied via settings
parameter in the :meth:`accept`, :meth:`validate`, :meth:`publish`, and
:meth:`finalize` methods.
The values also drive the custom UI defined by the plugin which allows
artists to manipulate the settings at runtime. See the
:meth:`create_settings_widget`, :meth:`set_ui_settings`, and
:meth:`get_ui_settings` for additional information.
.. note:: See the hooks defined in the publisher app's ``hooks/`` folder
for additional example implementations.
"""
return {}
@property
def item_filters(self):
"""
A :class:`list` of item type wildcard :class:`str` objects that this
plugin is interested in.
As items are collected by the collector hook, they are given an item
type string (see :meth:`~.api.PublishItem.create_item`). The strings
provided by this property will be compared to each collected item's
type.
Only items with types matching entries in this list will be considered
by the :meth:`accept` method. As such, this method makes it possible to
quickly identify which items the plugin may be interested in. Any
sophisticated acceptance logic is deferred to the :meth:`accept` method.
Strings can contain glob patters such as ``*``, for example ``["maya.*",
"file.maya"]``.
"""
raise NotImplementedError
############################################################################
# Publish processing methods
[docs] def accept(self, settings, item):
"""
This method is called by the publisher to see if the plugin accepts the
supplied item for processing.
Only items matching the filters defined via the :data:`item_filters`
property will be presented to this method.
A publish task will be generated for each item accepted here.
This method returns a :class:`dict` of the following form::
{
"accepted": <bool>,
"enabled": <bool>,
"visible": <bool>,
"checked": <bool>,
}
The keys correspond to the acceptance state of the supplied item. Not
all keys are required. The keys are defined as follows:
* ``accepted``: Indicates if the plugin is interested in this value at all.
If ``False``, no task will be created for this plugin. Required.
* ``enabled``: If ``True``, the created task will be enabled in the UI,
otherwise it will be disabled (no interaction allowed). Optional,
``True`` by default.
* ``visible``: If ``True``, the created task will be visible in the UI,
otherwise it will be hidden. Optional, ``True`` by default.
* ``checked``: If ``True``, the created task will be checked in the UI,
otherwise it will be unchecked. Optional, ``True`` by default.
In addition to the item, the configured settings for this plugin are
supplied. The information provided by each of these arguments can be
used to decide whether to accept the item.
For example, the item's ``properties`` :class:`dict` may house meta data
about the item, populated during collection. This data can be used to
inform the acceptance logic.
Example implementation:
.. code-block:: python
def accept(self, settings, item):
accept = True
# get the path for the item as set during collection
path = item.properties["path"]
# ensure the file is not too big
size_in_bytes = os.stat(path).st_stize
if size_in_bytes > math.pow(10, 9): # 1 GB
self.logger.warning("File is too big (> 1 GB)!")
accept = False
return {"accepted": accepted}
:param dict settings: The keys are strings, matching the keys returned
in the :data:`settings` property. The values are
:ref:`publish-api-setting` instances.
:param item: The :ref:`publish-api-item` instance to process for
acceptance.
:returns: dictionary with boolean keys accepted, required and enabled
"""
raise NotImplementedError
[docs] def validate(self, settings, item):
"""
Validates the given item, ensuring it is ok to publish.
Returns a boolean to indicate whether the item is ready to publish.
Returning ``True`` will indicate that the item is ready to publish. If
``False`` is returned, the publisher will disallow publishing of the
item.
An exception can also be raised to indicate validation failed.
When an exception is raised, the error message will be displayed as a
tooltip on the task as well as in the logging view of the publisher.
Simple implementation example for a Maya session item validation:
.. code-block:: python
def validate(self, settings, item):
path = cmds.file(query=True, sn=True)
# ensure the file has been saved
if not path:
raise Exception("The Maya session has not been saved.")
return True
:param dict settings: The keys are strings, matching the keys returned
in the :data:`settings` property. The values are
:ref:`publish-api-setting` instances.
:param item: The :ref:`publish-api-item` instance to validate.
:returns: True if item is valid, False otherwise.
"""
raise NotImplementedError
[docs] def publish(self, settings, item):
"""
Executes the publish logic for the given item and settings.
Any raised exceptions will indicate that the publish pass has failed and
the publisher will stop execution.
Simple implementation example for a Maya session item publish:
.. code-block:: python
def publish(self, settings, item):
path = item.properties["path"]
# ensure the session is saved
cmds.file(rename=path)
cmds.file(save=True, force=True)
# the hook's parent is the publisher
publisher = self.parent
# get the publish info
publish_version = publisher.util.get_version_number(path)
publish_name = publisher.util.get_publish_name(path)
# register the publish and pack the publish info into the item's
# properties dict
item.properties["sg_publish_data"] = sgtk.util.register_publish(
"tk": publisher.sgtk,
"context": item.context,
"comment": item.description,
"path": path,
"name": publish_name,
"version_number": publish_version,
"thumbnail_path": item.get_thumbnail_as_path(),
"published_file_type": "Maya Scene",
"dependency_paths": self._maya_get_session_dependencies()
)
:param dict settings: The keys are strings, matching the keys returned
in the :data:`settings` property. The values are
:ref:`publish-api-setting` instances.
:param item: The :ref:`publish-api-item` instance to publish.
"""
raise NotImplementedError
[docs] def finalize(self, settings, item):
"""
Execute the finalize logic for the given item and settings.
This method can be used to do any type of cleanup or reporting after
publishing is complete.
Any raised exceptions will indicate that the finalize pass has failed
and the publisher will stop execution.
Simple implementation example for a Maya session item finalization:
.. code-block:: python
def finalize(self, settings, item):
path = item.properties["path"]
# get the next version of the path
next_version_path = publisher.util.get_next_version_path(path)
# save to the next version path
cmds.file(rename=next_version_path)
cmds.file(save=True, force=True)
:param dict settings: The keys are strings, matching the keys returned
in the :data:`settings` property. The values are
:ref:`publish-api-setting` instances.
:param item: The :ref:`publish-api-item` instance to finalize.
"""
raise NotImplementedError
############################################################################
# Methods for creating/displaying custom plugin interface
# NOTE: We provide a default settings widget implementation here to show the
# plugin's description. This allows for a consistent default look and
# allows clients to write their own publish plugins while deferring custom
# UI settings implementations until needed.
[docs] def get_ui_settings(self, widget, items=None):
"""
Invoked by the Publisher when the selection changes. This method gathers the settings
on the previously selected task, so that they can be later used to repopulate the
custom UI if the task gets selected again. They will also be passed to the accept, validate,
publish and finalize methods, so that the settings can be used to drive the publish process.
The widget argument is the widget that was previously created by
`create_settings_widget`.
The method returns a dictionary, where the key is the name of a
setting that should be updated and the value is the new value of that
setting. Note that it is up to you how you want to store the UI's state as
settings and you don't have to necessarily to return all the values from
the UI. This is to allow the publisher to update a subset of settings
when multiple tasks have been selected.
Example::
{
"setting_a": "/path/to/a/file"
}
:param widget: The widget that was created by `create_settings_widget`
"""
# the default implementation does not show any editable widgets, so this
# is a no-op. this method is required to be defined in order for the
# custom UI to show up in the app
return {}
[docs] def set_ui_settings(self, widget, settings, items=None):
"""
Allows the custom UI to populate its fields with the settings from the
currently selected tasks.
The widget is the widget created and returned by
`create_settings_widget`.
A list of settings dictionaries are supplied representing the current
values of the settings for selected tasks. The settings dictionaries
correspond to the dictionaries returned by the settings property of the
hook.
Example::
settings = [
{
"seeting_a": "/path/to/a/file"
"setting_b": False
},
{
"setting_a": "/path/to/a/file"
"setting_b": False
}]
The default values for the settings will be the ones specified in the
environment file. Each task has its own copy of the settings.
When invoked with multiple settings dictionaries, it is the
responsibility of the custom UI to decide how to display the
information. If you do not wish to implement the editing of multiple
tasks at the same time, you can raise a ``NotImplementedError`` when
there is more than one item in the list and the publisher will inform
the user than only one task of that type can be edited at a time.
:param widget: The widget that was created by `create_settings_widget`.
:param settings: a list of dictionaries of settings for each selected
task.
:param items: A list of PublishItems the selected publish tasks are parented to.
"""
# the default implementation does not show any editable widgets, so this
# is a no-op. this method is required to be defined in order for the
# custom UI to show up in the app
pass