Source code for tk_multi_publish2.processing.item

# 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 collections
import inspect
import os
import tempfile

import sgtk
from ..base_hooks import PublishPlugin

logger = sgtk.platform.get_logger(__name__)


[docs]class Item(object): """ An object representing an scene or file item that should be processed. Items are constructed and returned by the collector hook. Items should always be created via the :meth:`Item.create_item` method of an existing item (typically the ``parent_item`` supplied to one of the collector's methods). The constructor should never be used within a collector. Items are organized as a tree with access to parent and children. """ def __init__(self, item_type, display_type, name, parent): """ Initialize an item. Should never be called. :param str item_type: Item type, typically following a hierarchical dot notation. For example, 'file', 'file.image', 'file.quicktime' or 'maya_scene' :param str display_type: Equivalent to the type, but for display purposes. :param str name: The name to represent the item in a UI. This can be a node name in a DCC or a file name. :param Item parent: Parent item. """ self._name = name self._type = item_type self._display_type = display_type self._parent = parent self._thumb_pixmap = None self._icon_pixmap = None self._children = [] self._tasks = [] self._context = None self._global_properties = _ItemProperties() self._local_properties = {} self._description = None self._created_temp_files = [] self._bundle = sgtk.platform.current_bundle() self._checked = True self._enabled = True self._expanded = True self._thumbnail_enabled = True self._allows_context_change = True # the following var indicates that the current thumbnail overrides the summary one self._thumbnail_explicit = True def __repr__(self): """ String representation """ if self._parent: return "<Item %s|%s:%s>" % (self._parent, self._type, self._name) else: return "<Item %s:%s>" % (self._type, self._name) def __del__(self): """ Destructor """ # clean up temp files created for temp_file in self._created_temp_files: if os.path.exists(temp_file): try: os.remove(temp_file) except Exception, e: logger.warning( "Could not remove temporary file '%s': %s" % (temp_file, e) ) else: logger.debug("Removed temp file '%s'" % temp_file) @classmethod def create_invisible_root_item(cls): """ Creates a root under which items can be parented. :returns: :class:`Item` """ return Item("_root", "_root", "_root", parent=None) def remove_item(self, item): """ Takes out the given child item from the list of children """ new_children = [] for child in self._children: if child != item: new_children.append(child) self._children = new_children def is_root(self): """ Checks if the current item is the root :returns: True if the root item, False otherwise """ return self.parent is None
[docs] def create_item(self, item_type, display_type, name): """ Factory method for generating new items. The ``item_type`` is a string that represents the type of the item. This can be any string, but is typically defined by studio convention. This value is used by the publish plugins to identify which items to act upon. The basic Shotgun integrations, for example, use a hierarchical dot notation such as: ``file.image``, ``file.image.sequence``, ``file.movie``, and ``maya.session``. The current convention used within the shipped integrations is to classify files that exist on disk as ``file.{type}`` (``file.image`` or ``file.video`` for example). This classification is determined from the mimetype in the base collector. In addition, any sequence-based items are classified as ``file.{type}.sequence`` (``file.image.sequence`` for example). For items defined within a DCC-session that must be save or exported prior to publish, the shipped integrations use the form ``{dcc}.{type}`` for the primary session item and ``{dcc}.{type}.{subtype}`` for items within the session. In Maya, for example, the primary session item would be of type ``maya.session`` and an item representing all geomtery in the session would be ``maya.session.geometry``. These are merely conventions used in the shipped integrations and can be altered to meet studio requirements. It is recommended to look at each of the publish plugins shipped with the publisher and housed in each of the toolkit engines to see what item types are supported by default. The ``display_type`` argument corresponds to the item type, but is used for display purposes only. Examples include: ``Image File``, ``Movie File``, and ``Maya Scene``. The ``name`` argument is the display name for the item instance. This can be the name of a file or a node name in Nuke or Houdini, or a combination of both. This value is displayed to the user and should make it easy for the user to identify what the item represents in terms of the current session or a file on disk. .. image:: ./resources/create_item_args.png | :param str item_type: Item type, typically following a hierarchical dot notation. :param str display_type: Equivalent to the type, but for display purposes. :param str name: The name to represent the item in a UI. This can be a node name in a DCC or a file name. """ child_item = Item(item_type, display_type, name, parent=self) self._children.append(child_item) child_item._parent = self logger.debug("Created %s" % child_item) return child_item
@property def parent(self): """ The parent item (read only) """ return self._parent @property def children(self): """ List of associated child items (read only) """ return self._children @property def properties(self): """ A free form dictionary-like object where arbitrary data can be stored on the item. The properties dictionary itself is read only (calling ``item.properties = my_properties`` is invalid) but arbitrary key value pairs can be set within the dictionary itself. This property provides a way to store data that is global across all attached publish plugins. It is also useful for accessing data stored on parent items that may be useful to plugin attached to child items. For properties that are local to the current plugin, see ``local_properties``. This property can also be used to store data on an items that may then be accessed by plugins attached to the item's children. """ return self._global_properties @property def local_properties(self): """ A dictionary-like object that houses item properties local to the current publish plugin instance. This property behaves like the local storage in python's threading module, except here, the data is local to the current publish plugin. You can get and set values for this property using standard dictionary notation or via dot notation. Attempts to access this property outside of a publish plugin will raise an ``AttributeError``. It is important to consider when to set a value via :meth:`Item.properties`` and when to use :meth:`Item.local_properties`. Setting the values on ``item.properties`` is a way to globally share information between publish plugins. Values set via ``item.local_properties`` will only be applied during the execution of the current plugin (similar to python's ``threading.local`` storage). A common scenario to consider is when you have multiple publish plugins acting on the same item. You may, for example, want the ``publish_name`` and ``publish_version`` to be shared by each plugin, while setting the remaining properties on each plugin instance since they will be specific to that plugin's output. Example:: # set shared properties on the item (potentially in the collector or # the first publish plugin). these values will be available to all # publish plugins attached to the item. item.properties.publish_name = "Gorilla" item.properties.publish_version = "0003" # set specific properties in subclasses of the base file publish (this # class). first publish plugin... item.local_properties.publish_template = "asset_fbx_template" item.local_properties.publish_type = "FBX File" # in another publish plugin... item.local_properties.publish_template = "asset_abc_template" item.local_properties.publish_type = "Alembic Cache" """ return self._get_local_properties() def _get_local_properties(self): """ This is done in a separate method to allow access to any method in this class. We look 2 levels up in the stack to get the calling plugin class. If this is called more than one level deep in this class, expect issues. """ # try to determine the current publish plugin calling_object = inspect.stack()[2][0].f_locals.get("self") if not calling_object or not isinstance(calling_object, PublishPlugin): raise AttributeError( "Could not determine the current publish plugin when accessing " "an item's local properties. Item: %s" % (self,)) if calling_object not in self._local_properties: self._local_properties[calling_object] = _ItemProperties() return self._local_properties[calling_object]
[docs] def get_property(self, name, default_value=None): """ This is a convenience method that will retrieve a property set on the item. If the property was set via :meth:`Item.local_properties`, then that will be returned. Otherwise, the value set via :meth:`Item.properties` will be returned. If the property is not set on the item, then the supplied ``default_value`` will be returned (default is ``None``). :param name: The property to retrieve. :param default_value: The value to return if the property is not set on the item. :return: The value of the supplied property. """ local_properties = self._get_local_properties() return local_properties.get(name) or self.properties.get(name) or \ default_value
@property def tasks(self): """ Tasks associated with this item. """ return self._tasks def add_task(self, task): """ Adds a task to this item :param task: Task instance to be added """ self._tasks.append(task) def _get_name(self): """ The name of the item as a string. """ return self._name def _set_name(self, name): # setter for name self._name = name name = property(_get_name, _set_name) def _get_type(self): """ The item type as defined when Publish item was created. See :meth:`Item.create_item` for more info. """ return self._type def _set_type(self, item_type): # setter for type self._type = item_type type = property(_get_type, _set_type) def _get_display_type(self): """ A human readable type string, suitable for UI and display purposes. """ return self._display_type def _set_display_type(self, display_type): # setter for display_type self._display_type = display_type display_type = property(_get_display_type, _set_display_type) def _get_thumbnail_enabled(self): """ Flag to indicate that thumbnails can be interacted with. * If ``True``, thumbnails will be visible and editable in the publish UI (via screen capture). * If ``False`` and a thumbnail has been set via the :meth:`thumbnail` property, the thumbnail will be visible but screen capture will be disabled. * If ``False`` and no thumbnail has been specified, no thumbnail will appear in the UI. """ return self._thumbnail_enabled def _set_thumbnail_enabled(self, enabled): # setter for thumbnail_enabled self._thumbnail_enabled = enabled thumbnail_enabled = property(_get_thumbnail_enabled, _set_thumbnail_enabled) def _get_thumbnail_explicit(self): """ Flag to indicate that thumbnail has been explicitly set. When this flag is on, the summary thumbnail should be ignored For this this specific item. """ return self._thumbnail_explicit def _set_thumbnail_explicit(self, enabled): """ Setter for _thumbnail_explicit """ self._thumbnail_explicit = enabled thumbnail_explicit = property(_get_thumbnail_explicit,_set_thumbnail_explicit) def _get_context(self): """ The context associated with this item. If no context has been defined, the parent context will be returned or if that hasn't been defined, :class:`None` will be returned. """ if self._context: return self._context elif self.parent: return self.parent.context else: return self._bundle.context def _set_context(self, context): # setter for context property self._context = context context = property(_get_context, _set_context) def _get_description(self): """ The description for this item that will be displayed to the user and associated with the eventual Publish in Shotgun. If no description has been set for this item, the parent item's description will be returned. If no description has been set for the parent, None will be returned. """ if self._description: return self._description elif self.parent: return self.parent.description else: return None def _set_description(self, description): # setter for description property self._description = description def _propagate_description_to_children(self): """ propagate description to children """ for child in self._children: child.description = self._description description = property(_get_description, _set_description) def _get_thumbnail_explicit_recursive(self): """ Returns true is item or any of its children is explicit """ if self.thumbnail_explicit: return True for child in self._children: if child._get_thumbnail_explicit_recursive(): return True return False def _propagate_thumbnail_to_children(self): """ propagate thumbnail to children """ for child in self._children: child.thumbnail = self.thumbnail child.thumbnail_explicit = False child._propagate_thumbnail_to_children() def _get_thumbnail(self): """ The associated thumbnail, as a :class:`QtGui.QPixmap`. The thumbnail is an image to represent the item visually such as a thumbnail of an image or a screenshot of a scene. If no thumbnail has been defined for this node, the parent thumbnail is returned, or None if no thumbnail exists. """ if self._thumb_pixmap: return self._thumb_pixmap elif self.parent: return self.parent.thumbnail else: return None def _set_thumbnail(self, pixmap): self._thumb_pixmap = pixmap thumbnail = property(_get_thumbnail, _set_thumbnail) def _get_expanded(self): """ Flag to indicate that this item's children should be expanded. """ return self._expanded def _set_expanded(self, expand_state): # setter for expanded self._expanded = expand_state expanded = property(_get_expanded, _set_expanded) def _get_checked(self): """ Flag to indicate that this item should be checked by default. Please note that the final state of the node is also affected by the child tasks. Below are some examples of how this interaction plays out in practice: - If all child tasks/items return ``checked: False`` in their accept method, the parent item will be unchecked, regardless of the state of this property. - If one or more child tasks return ``checked: True`` and the item checked state is False, the item and all its sub-items will be unchecked. """ return self._checked def _set_checked(self, check_state): # setter for checked self._checked = check_state checked = property(_get_checked, _set_checked) def _get_enabled(self): """ Flag to indicate that this item and its children should be enabled. """ return self._enabled def _set_enabled(self, enabled): # setter for enabled self._enabled = enabled enabled = property(_get_enabled, _set_enabled)
[docs] def set_thumbnail_from_path(self, path): """ Helper method. Parses the contents of the given file path and tries to convert it into a QPixmap which is then used to set the thumbnail for the item. :param str path: Path to a file on disk """ # TODO: this needs to be refactored. should be no UI stuff here from sgtk.platform.qt import QtGui try: self._thumb_pixmap = QtGui.QPixmap(path) except Exception, e: logger.warning( "%r: Could not load '%s': %s" % (self, path, e) )
[docs] def get_thumbnail_as_path(self): """ Helper method. Writes the associated thumbnail to a temp file on disk and returns the path. This path is automatically deleted when the object goes out of scope. :returns: Path to a file on disk or None if no thumbnail set """ if self.thumbnail is None: return None temp_path = tempfile.NamedTemporaryFile( suffix=".jpg", prefix="sgtk_thumb", delete=False ).name success = self.thumbnail.save(temp_path) if success: if os.path.getsize(temp_path) > 0: self._created_temp_files.append(temp_path) else: logger.debug( "A zero-size thumbnail was written for %s, " "no thumbnail will be uploaded." % self.name ) return None return temp_path else: logger.warning( "Thumbnail save to disk failed. No thumbnail will be uploaded for %s." % self.name ) return None
@property def icon(self): """ The associated icon, as a QPixmap. The icon is a small square image used to represent the item visually .. image:: ./resources/item_icon.png | If no icon has been defined for this node, the parent icon is returned, or a default one if not defined """ # TODO: this needs to be refactored. should be no UI stuff here from sgtk.platform.qt import QtGui if self._icon_pixmap: return self._icon_pixmap elif self.parent: return self.parent.icon else: # return default return QtGui.QPixmap(":/tk_multi_publish2/item.png")
[docs] def set_icon_from_path(self, path): """ Helper method. Parses the contents of the given file path and tries to convert it into a QPixmap which is then used to set the icon for the item. :param str path: Path to a file on disk """ # TODO: this needs to be refactored. should be no UI stuff here from sgtk.platform.qt import QtGui try: self._icon_pixmap = QtGui.QPixmap(path) except Exception, e: logger.warning( "%r: Could not load icon '%s': %s" % (self, path, e) )
@property def context_change_allowed(self): """ True if item allows context change, False otherwise. Default is True """ return self._allows_context_change @context_change_allowed.setter def context_change_allowed(self, allow): """ Enable/disable context change for this item. """ self._allows_context_change = allow
class _ItemProperties(collections.MutableMapping): """ A dictionary-like object for storing arbitrary item properties. Provides access via standard dict syntax as well as dot notation. """ def __init__(self, **kwargs): """ Initialize the item properties. This allows an instance to be created with supplied key/value pairs. """ self.__dict__.update(**kwargs) def __setitem__(self, key, value): self.__dict__[key] = value def __getitem__(self, key): return self.__dict__[key] def __delitem__(self, key): del self.__dict__[key] def __iter__(self): return iter(self.__dict__) def __len__(self): return len(self.__dict__)