Source code for collector

# 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 os
import hou
import sgtk

[docs]HookBaseClass = sgtk.get_hook_baseclass()
# A dict of dicts organized by category, type and output file parm
[docs]_HOUDINI_OUTPUTS = { # rops hou.ropNodeTypeCategory(): [ "alembic", # alembic cache "geometry", # geometry "ifd", # mantra render node "arnold", # arnold render node ], # sops hou.sopNodeTypeCategory(): [ "rop_alembic", # alembic cache "rop_geometry", # geometry
], }
[docs]def _iter_output_nodes(): """Iterate over all output nodes in the scene. For a more functional programming approach, you can try:: for category, type_names in _HOUDINI_OUTPUTS.items(): node_types = (hou.nodeType(category, name) for name in type_names) for node_type in filter(None, node_types): # Strip None node_type yield category, node_type, node_type.instances() or () :return: Node category, node type and all node instances. :rtype: Generator[hou.NodeTypeCategory, hou.NodeType, tuple[hou.Node]] """ for category, type_names in _HOUDINI_OUTPUTS.items(): for name in type_names: node_type = hou.nodeType(category, name) if node_type is not None: yield category, node_type, node_type.instances() or ()
[docs]class HoudiniSessionCollector(HookBaseClass): """ Collector that operates on the current houdini session. Should inherit from the basic collector hook. """
[docs] def _get_icon_path(self, icon_name, icons_folders=None): icons_path = os.path.join(self.disk_location, "icons") if icons_folders: icons_folders.append(icons_path) else: icons_folders = [icons_path] return super(HoudiniSessionCollector, self)._get_icon_path( icon_name, icons_folders=icons_folders
) @property
[docs] def common_file_info(self): """Get a mapping of file types information. Sets up/stores it as `self._common_file_info` if not initialised. :return: File types information :rtype: dict[str, dict[str]] """ if not hasattr(self, "_common_file_info"): # do this once to avoid unnecessary processing self._common_file_info = { "Alembic Cache": { "extensions": ["abc"], "icon": self._get_icon_path("alembic.png"), "item_type": "file.alembic", }, "Geometry Cache": { "extensions": ["geo", "bgeo.sc", "sc", "vdb"], "icon": self._get_icon_path("geometry.png"), "item_type": "file.houdini.geometry", }, "Rendered Image": { "extensions": ["exr", "rat"], "icon": self._get_icon_path("image_sequence.png"), "item_type": "file.image", }, "Ass File": { "extensions": ["ass"], "icon": self._get_icon_path("ass_file.png"), "item_type": "file.arnold.ass", }, "Ifd Cache": { "extensions": ["ifd"], "icon": self._get_icon_path("ifd_file.png"), "item_type": "file.houdini.ifd", }, "Material X File": { "extensions": ["mtlx"], "icon": self._get_icon_path("materialX.png"), "item_type": "file.arnold.mtlx", }, } return self._common_file_info
@property
[docs] def settings(self): """ Dictionary defining the settings that this collector expects to receive through the settings parameter in the process_current_session and process_file methods. A dictionary on the following form:: { "Settings Name": { "type": "settings_type", "default": "default_value", "description": "One line description of the setting" } The type string should be one of the data types that toolkit accepts as part of its environment configuration. """ # grab any base class settings collector_settings = super(HoudiniSessionCollector, self).settings or {} # settings specific to this collector houdini_session_settings = { "Work Template": { "type": "template", "default": None, "description": ( "Template path for artist work files. Should " "correspond to a template defined in " "templates.yml. If configured, is made available" "to publish plugins via the collected item's " "properties. " ), }, } # update the base settings with these settings collector_settings.update(houdini_session_settings) return collector_settings
[docs] def process_current_session(self, settings, parent_item): """ Analyzes the current Houdini session and parents a subtree of items under the parent_item passed in. :param dict settings: Configured settings for this collector :param parent_item: Root item instance """ # create an item representing the current houdini session item = self.collect_current_houdini_session(settings, parent_item) # collect other, non-toolkit outputs to present for publishing self.collect_node_outputs(settings, item)
[docs] def collect_current_houdini_session(self, settings, parent_item): """ Creates an item that represents the current houdini session. :param dict settings: Configured settings for this collector :param parent_item: Parent Item instance :returns: Item of type houdini.session """ publisher = self.parent # get the path to the current file path = hou.hipFile.path() # determine the display name for the item if path: file_info = publisher.util.get_file_path_components(path) display_name = file_info["filename"] else: display_name = "Current Houdini Session" # create the session item for the publish hierarchy session_item = parent_item.create_item( "houdini.session", "Houdini File", display_name ) # get the icon path to display for this item icon_path = os.path.join(self.disk_location, "icons", "houdini.png") session_item.set_icon_from_path(icon_path) # if a work template is defined, add it to the item properties so that # it can be used by attached publish plugins work_template_setting = settings.get("Work Template") if work_template_setting: work_template = publisher.engine.get_template_by_name( work_template_setting.value ) # store the template on the item for use by publish plugins. we # can't evaluate the fields here because there's no guarantee the # current session path won't change once the item has been created. # the attached publish plugins will need to resolve the fields at # execution time. session_item.properties["work_template"] = work_template self.logger.debug("Work template defined for Houdini collection.") self.logger.info("Collected current Houdini session") return session_item
[docs] def collect_node_outputs(self, settings, parent_item): """ Creates items for known output nodes :param dict settings: Configured settings for this collector :param parent_item: Parent Item instance """ engine = self.parent.engine for _, node_type, nodes in _iter_output_nodes(): type_name = node_type.name() get_output_paths_and_templates = None # iterate over each node for node in nodes: if get_output_paths_and_templates is None: get_output_paths_and_templates = getattr( engine.node_handler(node), "get_output_paths_and_templates", None, ) if get_output_paths_and_templates is None: # This isn't an export node or missing handler self.logger.info("%s node: %s", type_name, node.path()) break # get the evaluated path parm value self.logger.info("Processing %s node: %s", type_name, node.path()) paths_and_templates = get_output_paths_and_templates(node) node_item = parent_item.create_item( "houdini.node.{}".format(type_name), "", node.name() ) for path_and_templates in paths_and_templates: path = path_and_templates["path"] # Check that something was generated # Path might point to an image number that doesn't exist, so better # check the sequence paths also as these have been found already if not ( os.path.exists(path) or path_and_templates.get("sequence_paths") ): continue self.logger.info("Processing %s node: %s", node_type, node.path()) is_sequence = "sequence_paths" in path_and_templates # allow the base class to collect and create the item. it # should know how to handle the output path item = super(HoudiniSessionCollector, self)._collect_file( node_item, path, frame_sequence=is_sequence ) if ( item.type_spec == "file.image" and "is_deep" in path_and_templates ): # Handle deep renders, which can only be exr files annoyingly sequence = " Sequence" if is_sequence else "" item.type_spec = "file.image.deep{}".format( sequence.lower().replace(" ", ".") ) item.name = "Deep Image{}".format(sequence) item.properties["publish_type"] = "Deep Image" item.set_icon_from_path(self._get_icon_path("deep_image.png")) if is_sequence: # self._collect_file doesn't fill in # sequence_paths correctly so we must do it sequence_paths = path_and_templates["sequence_paths"] item.properties["sequence_paths"] = sequence_paths template = path_and_templates["work_template"] item.properties["work_template"] = template item.properties["publish_template"] = path_and_templates[ "publish_template" ] fields = template.get_fields(path) publish_name_tokens = [] for key in ["name", "location", "variation", "identifier"]: token = fields.get(key) if token: publish_name_tokens.append(token.title()) publish_name = "{} ({})".format( ", ".join(publish_name_tokens), item.type_display ) item.properties["publish_name"] = publish_name