diff --git a/hooks/tk-multi-publish2/basic/publish_asset.py b/hooks/tk-multi-publish2/basic/publish_asset.py index ba7df2d..6085010 100644 --- a/hooks/tk-multi-publish2/basic/publish_asset.py +++ b/hooks/tk-multi-publish2/basic/publish_asset.py @@ -4,9 +4,20 @@ import sgtk import os +import sys import unreal import datetime + +# Local storage path field for known Oses. +_OS_LOCAL_STORAGE_PATH_FIELD = { + "darwin": "mac_path", + "win32": "windows_path", + "linux": "linux_path", + "linux2": "linux_path", +}[sys.platform] + + HookBaseClass = sgtk.get_hook_baseclass() @@ -70,7 +81,12 @@ def settings(self): "description": "Template path for published work files. Should" "correspond to a template defined in " "templates.yml.", - } + }, + "Publish Folder": { + "type": "string", + "default": None, + "description": "Optional folder to use as a root for publishes" + }, } # update the base settings @@ -89,6 +105,151 @@ def item_filters(self): """ return ["unreal.asset.StaticMesh"] + def create_settings_widget(self, parent): + """ + Creates a Qt widget, for the supplied parent widget (a container widget + on the right side of the publish UI). + + :param parent: The parent to use for the widget being created. + :returns: A :class:`QtGui.QFrame` that displays editable widgets for + modifying the plugin's settings. + """ + # defer Qt-related imports + from sgtk.platform.qt import QtGui, QtCore + + # Create a QFrame with all our widgets + settings_frame = QtGui.QFrame(parent) + # Create our widgets, we add them as properties on the QFrame so we can + # retrieve them easily. Qt uses camelCase so our xxxx_xxxx names can't + # clash with existing Qt properties. + + # Show this plugin description + settings_frame.description_label = QtGui.QLabel(self.description) + settings_frame.description_label.setWordWrap(True) + settings_frame.description_label.setOpenExternalLinks(True) + settings_frame.description_label.setTextFormat(QtCore.Qt.RichText) + + # Unreal setttings + settings_frame.unreal_publish_folder_label = QtGui.QLabel("Publish folder:") + storage_roots = self.parent.shotgun.find( + "LocalStorage", + [], + ["code", _OS_LOCAL_STORAGE_PATH_FIELD] + ) + settings_frame.storage_roots_widget = QtGui.QComboBox() + settings_frame.storage_roots_widget.addItem("Current Unreal Project") + for storage_root in storage_roots: + if storage_root[_OS_LOCAL_STORAGE_PATH_FIELD]: + settings_frame.storage_roots_widget.addItem( + "%s (%s)" % ( + storage_root["code"], + storage_root[_OS_LOCAL_STORAGE_PATH_FIELD] + ), + userData=storage_root, + ) + # Create the layout to use within the QFrame + settings_layout = QtGui.QVBoxLayout() + settings_layout.addWidget(settings_frame.description_label) + settings_layout.addWidget(settings_frame.unreal_publish_folder_label) + settings_layout.addWidget(settings_frame.storage_roots_widget) + + settings_layout.addStretch() + settings_frame.setLayout(settings_layout) + return settings_frame + + def get_ui_settings(self, widget): + """ + Method called by the publisher to retrieve setting values from the UI. + + :returns: A dictionary with setting values. + """ + # defer Qt-related imports + from sgtk.platform.qt import QtCore + + self.logger.info("Getting settings from UI") + + # Please note that we don't have to return all settings here, just the + # settings which are editable in the UI. + storage_index = widget.storage_roots_widget.currentIndex() + publish_folder = None + if storage_index > 0: # Something selected and not the first entry + storage = widget.storage_roots_widget.itemData(storage_index, role=QtCore.Qt.UserRole) + publish_folder = storage[_OS_LOCAL_STORAGE_PATH_FIELD] + + settings = { + "Publish Folder": publish_folder, + } + return settings + + def set_ui_settings(self, widget, settings): + """ + Method called by the publisher to populate the UI with the setting values. + + :param widget: A QFrame we created in `create_settings_widget`. + :param settings: A list of dictionaries. + :raises NotImplementedError: if editing multiple items. + """ + # defer Qt-related imports + from sgtk.platform.qt import QtCore + + self.logger.info("Setting UI settings") + if len(settings) > 1: + # We do not allow editing multiple items + raise NotImplementedError + cur_settings = settings[0] + # Note: the template is validated in the accept method, no need to check it here. + publish_template_setting = cur_settings.get("Publish Template") + publisher = self.parent + publish_template = publisher.get_template_by_name(publish_template_setting) + if isinstance(publish_template, sgtk.TemplatePath): + widget.unreal_publish_folder_label.setEnabled(False) + widget.storage_roots_widget.setEnabled(False) + folder_index = 0 + publish_folder = cur_settings["Publish Folder"] + if publish_folder: + for i in range(widget.storage_roots_widget.count()): + data = widget.storage_roots_widget.itemData(i, role=QtCore.Qt.UserRole) + if data and data[_OS_LOCAL_STORAGE_PATH_FIELD] == publish_folder: + folder_index = i + break + self.logger.debug("Index for %s is %s" % (publish_folder, folder_index)) + widget.storage_roots_widget.setCurrentIndex(folder_index) + + def load_saved_ui_settings(self, settings): + """ + Load saved settings and update the given settings dictionary with them. + + :param settings: A dictionary where keys are settings names and + values Settings instances. + """ + # Retrieve SG utils framework settings module and instantiate a manager + fw = self.load_framework("tk-framework-shotgunutils_v5.x.x") + module = fw.import_module("settings") + settings_manager = module.UserSettings(self.parent) + + # Retrieve saved settings + settings["Publish Folder"].value = settings_manager.retrieve( + "publish2.publish_folder", + settings["Publish Folder"].value, + settings_manager.SCOPE_PROJECT + ) + self.logger.debug("Loaded settings %s" % settings["Publish Folder"]) + + def save_ui_settings(self, settings): + """ + Save UI settings. + + :param settings: A dictionary of Settings instances. + """ + # Retrieve SG utils framework settings module and instantiate a manager + fw = self.load_framework("tk-framework-shotgunutils_v5.x.x") + module = fw.import_module("settings") + settings_manager = module.UserSettings(self.parent) + + # Save settings + publish_folder = settings["Publish Folder"].value + settings_manager.store("publish2.publish_folder", publish_folder, settings_manager.SCOPE_PROJECT) + def accept(self, settings, item): """ Method called by the publisher to determine if an item is of any @@ -132,6 +293,7 @@ def accept(self, settings, item): # for use in subsequent methods item.properties["publish_template"] = publish_template + self.load_saved_ui_settings(settings) return { "accepted": accepted, "checked": True @@ -172,10 +334,7 @@ def validate(self, settings, item): self.logger.debug("Asset path or name not configured.") return False - publish_template = item.properties.get("publish_template") - if not publish_template: - self.logger.debug("No publish template configured.") - return False + publish_template = item.properties["publish_template"] # Add the Unreal asset name to the fields fields = {"name": asset_name} @@ -194,10 +353,22 @@ def validate(self, settings, item): # which should be project root + publish template publish_path = publish_template.apply_fields(fields) publish_path = os.path.normpath(publish_path) + if not os.path.isabs(publish_path): + # If the path is not absolute, prepend the publish folder setting. + publish_folder = settings["Publish Folder"].value + if not publish_folder: + publish_folder = unreal.Paths.project_saved_dir() + publish_path = os.path.abspath( + os.path.join( + publish_folder, + publish_path + ) + ) + item.properties["publish_path"] = publish_path item.properties["path"] = publish_path - # Remove the filename from the work path - destination_path = os.path.split(publish_path)[0] + # Remove the filename from the publish path + destination_path = os.path.dirname(publish_path) # Stash the destination path in properties item.properties["destination_path"] = destination_path @@ -207,7 +378,7 @@ def validate(self, settings, item): # run the base class validation # return super(UnrealAssetPublishPlugin, self).validate(settings, item) - + self.save_ui_settings(settings) return True def publish(self, settings, item): diff --git a/hooks/tk-multi-publish2/basic/publish_movie.py b/hooks/tk-multi-publish2/basic/publish_movie.py index 8d14517..b207178 100644 --- a/hooks/tk-multi-publish2/basic/publish_movie.py +++ b/hooks/tk-multi-publish2/basic/publish_movie.py @@ -14,6 +14,13 @@ import sys import tempfile +# Local storage path field for known Oses. +_OS_LOCAL_STORAGE_PATH_FIELD = { + "darwin": "mac_path", + "win32": "windows_path", + "linux": "linux_path", + "linux2": "linux_path", +}[sys.platform] HookBaseClass = sgtk.get_hook_baseclass() @@ -89,6 +96,11 @@ def settings(self): "default": None, "description": "Optional Unreal Path to saved presets " "for rendering with the Movie Render Queue" + }, + "Publish Folder": { + "type": "string", + "default": None, + "description": "Optional folder to use as a root for publishes" } } @@ -139,11 +151,31 @@ def create_settings_widget(self, parent): presets_folder = unreal.MovieRenderPipelineProjectSettings().preset_save_dir for preset in unreal.EditorAssetLibrary.list_assets(presets_folder.path): settings_frame.unreal_render_presets_widget.addItem(preset.split(".")[0]) + + settings_frame.unreal_publish_folder_label = QtGui.QLabel("Publish folder:") + storage_roots = self.parent.shotgun.find( + "LocalStorage", + [], + ["code", _OS_LOCAL_STORAGE_PATH_FIELD] + ) + settings_frame.storage_roots_widget = QtGui.QComboBox() + settings_frame.storage_roots_widget.addItem("Current Unreal Project") + for storage_root in storage_roots: + if storage_root[_OS_LOCAL_STORAGE_PATH_FIELD]: + settings_frame.storage_roots_widget.addItem( + "%s (%s)" % ( + storage_root["code"], + storage_root[_OS_LOCAL_STORAGE_PATH_FIELD] + ), + userData=storage_root, + ) # Create the layout to use within the QFrame settings_layout = QtGui.QVBoxLayout() settings_layout.addWidget(settings_frame.description_label) settings_layout.addWidget(settings_frame.unreal_render_presets_label) settings_layout.addWidget(settings_frame.unreal_render_presets_widget) + settings_layout.addWidget(settings_frame.unreal_publish_folder_label) + settings_layout.addWidget(settings_frame.storage_roots_widget) settings_layout.addStretch() settings_frame.setLayout(settings_layout) @@ -155,6 +187,9 @@ def get_ui_settings(self, widget): :returns: A dictionary with setting values. """ + # defer Qt-related imports + from sgtk.platform.qt import QtCore + self.logger.info("Getting settings from UI") # Please note that we don't have to return all settings here, just the @@ -162,8 +197,15 @@ def get_ui_settings(self, widget): render_presets_path = None if widget.unreal_render_presets_widget.currentIndex() > 0: # First entry is "No Presets" render_presets_path = six.ensure_str(widget.unreal_render_presets_widget.currentText()) + storage_index = widget.storage_roots_widget.currentIndex() + publish_folder = None + if storage_index > 0: # Something selected and not the first entry + storage = widget.storage_roots_widget.itemData(storage_index, role=QtCore.Qt.UserRole) + publish_folder = storage[_OS_LOCAL_STORAGE_PATH_FIELD] + settings = { "Movie Render Queue Presets Path": render_presets_path, + "Publish Folder": publish_folder, } return settings @@ -175,6 +217,9 @@ def set_ui_settings(self, widget, settings): :param settings: A list of dictionaries. :raises NotImplementedError: if editing multiple items. """ + # defer Qt-related imports + from sgtk.platform.qt import QtCore + self.logger.info("Setting UI settings") if len(settings) > 1: # We do not allow editing multiple items @@ -186,6 +231,66 @@ def set_ui_settings(self, widget, settings): preset_index = widget.unreal_render_presets_widget.findText(render_presets_path) self.logger.info("Index for %s is %s" % (render_presets_path, preset_index)) widget.unreal_render_presets_widget.setCurrentIndex(preset_index) + # Note: the template is validated in the accept method, no need to check it here. + publish_template_setting = cur_settings.get("Publish Template") + publisher = self.parent + publish_template = publisher.get_template_by_name(publish_template_setting) + if isinstance(publish_template, sgtk.TemplatePath): + widget.unreal_publish_folder_label.setEnabled(False) + widget.storage_roots_widget.setEnabled(False) + folder_index = 0 + publish_folder = cur_settings["Publish Folder"] + if publish_folder: + for i in range(widget.storage_roots_widget.count()): + data = widget.storage_roots_widget.itemData(i, role=QtCore.Qt.UserRole) + if data and data[_OS_LOCAL_STORAGE_PATH_FIELD] == publish_folder: + folder_index = i + break + self.logger.debug("Index for %s is %s" % (publish_folder, folder_index)) + widget.storage_roots_widget.setCurrentIndex(folder_index) + + def load_saved_ui_settings(self, settings): + """ + Load saved settings and update the given settings dictionary with them. + + :param settings: A dictionary where keys are settings names and + values Settings instances. + """ + # Retrieve SG utils framework settings module and instantiate a manager + fw = self.load_framework("tk-framework-shotgunutils_v5.x.x") + module = fw.import_module("settings") + settings_manager = module.UserSettings(self.parent) + + # Retrieve saved settings + settings["Movie Render Queue Presets Path"].value = settings_manager.retrieve( + "publish2.movie_render_queue_presets_path", + settings["Movie Render Queue Presets Path"].value, + settings_manager.SCOPE_PROJECT, + ) + settings["Publish Folder"].value = settings_manager.retrieve( + "publish2.publish_folder", + settings["Publish Folder"].value, + settings_manager.SCOPE_PROJECT + ) + self.logger.debug("Loaded settings %s" % settings["Publish Folder"]) + self.logger.debug("Loaded settings %s" % settings["Movie Render Queue Presets Path"]) + + def save_ui_settings(self, settings): + """ + Save UI settings. + + :param settings: A dictionary of Settings instances. + """ + # Retrieve SG utils framework settings module and instantiate a manager + fw = self.load_framework("tk-framework-shotgunutils_v5.x.x") + module = fw.import_module("settings") + settings_manager = module.UserSettings(self.parent) + + # Save settings + render_presets_path = settings["Movie Render Queue Presets Path"].value + settings_manager.store("publish2.movie_render_queue_presets_path", render_presets_path, settings_manager.SCOPE_PROJECT) + publish_folder = settings["Publish Folder"].value + settings_manager.store("publish2.publish_folder", publish_folder, settings_manager.SCOPE_PROJECT) def accept(self, settings, item): """ @@ -222,14 +327,14 @@ def accept(self, settings, item): if not publish_template: self.logger.debug( "A publish template could not be determined for the " - "sequence item. Not accepting the item." + "item. Not accepting the item." ) accepted = False # we've validated the work and publish templates. add them to the item properties # for use in subsequent methods item.properties["publish_template"] = publish_template - + self.load_saved_ui_settings(settings) return { "accepted": accepted, "checked": True @@ -271,7 +376,7 @@ def validate(self, settings, item): return False # Get the configured publish template - publish_template = item.properties.get("publish_template") + publish_template = item.properties["publish_template"] # Get the context from the Publisher UI context = item.context @@ -359,11 +464,23 @@ def validate(self, settings, item): self.logger.error(error_msg) raise ValueError(error_msg) - item.properties["path"] = publish_template.apply_fields(fields) - item.properties["publish_path"] = item.properties["path"] + publish_path = publish_template.apply_fields(fields) + if not os.path.isabs(publish_path): + # If the path is not absolute, prepend the publish folder setting. + publish_folder = settings["Publish Folder"].value + if not publish_folder: + publish_folder = unreal.Paths.project_saved_dir() + publish_path = os.path.abspath( + os.path.join( + publish_folder, + publish_path + ) + ) + item.properties["path"] = publish_path + item.properties["publish_path"] = publish_path item.properties["publish_type"] = "Unreal Render" item.properties["version_number"] = version_number - + self.save_ui_settings(settings) return True def _check_render_settings(self, render_config): @@ -404,8 +521,7 @@ def publish(self, settings, item): # let the base class register the publish - publish_path = item.properties.get("path") - publish_path = os.path.normpath(publish_path) + publish_path = os.path.normpath(item.properties["publish_path"]) # Split the destination path into folder and filename destination_folder, movie_name = os.path.split(publish_path) @@ -503,8 +619,6 @@ def finalize(self, settings, item): # do the base class finalization super(UnrealMoviePublishPlugin, self).finalize(settings, item) - pass - def _get_version_entity(self, item): """ Returns the best entity to link the version to. diff --git a/info.yml b/info.yml index 577bbd1..cd74090 100644 --- a/info.yml +++ b/info.yml @@ -35,4 +35,5 @@ requires_core_version: "v0.18.8" frameworks: - {"name": "tk-framework-unrealqt", "version": "v1.x.x"} + - {"name": "tk-framework-shotgunutils", "version": "v5.x.x"}