diff --git a/pype/hooks/fusion/prelaunch.py b/pype/hooks/fusion/prelaunch.py new file mode 100644 index 0000000000..69e91eda05 --- /dev/null +++ b/pype/hooks/fusion/prelaunch.py @@ -0,0 +1,61 @@ +import os +import traceback +import importlib +from pype.lib import PypeHook +from pypeapp import Logger +from pype.hosts.fusion import utils + + +class FusionPrelaunch(PypeHook): + """ + This hook will check if current workfile path has Fusion + project inside. + """ + + def __init__(self, logger=None): + if not logger: + self.log = Logger().get_logger(self.__class__.__name__) + else: + self.log = logger + + self.signature = "( {} )".format(self.__class__.__name__) + + def execute(self, *args, env: dict = None) -> bool: + + if not env: + env = os.environ + + # making sure pyton 3.6 is installed at provided path + py36_dir = os.path.normpath(env.get("PYTHON36", "")) + assert os.path.isdir(py36_dir), ( + "Python 3.6 is not installed at the provided folder path. Either " + "make sure the `environments\resolve.json` is having correctly " + "set `PYTHON36` or make sure Python 3.6 is installed " + f"in given path. \nPYTHON36E: `{py36_dir}`" + ) + self.log.info(f"Path to Fusion Python folder: `{py36_dir}`...") + env["PYTHON36"] = py36_dir + + # setting utility scripts dir for scripts syncing + us_dir = os.path.normpath(env.get("FUSION_UTILITY_SCRIPTS_DIR", "")) + assert os.path.isdir(us_dir), ( + "Fusion utility script dir does not exists. Either make sure " + "the `environments\fusion.json` is having correctly set " + "`FUSION_UTILITY_SCRIPTS_DIR` or reinstall DaVinci Resolve. \n" + f"FUSION_UTILITY_SCRIPTS_DIR: `{us_dir}`" + ) + + try: + __import__("avalon.fusion") + __import__("pyblish") + + except ImportError as e: + print(traceback.format_exc()) + print("pyblish: Could not load integration: %s " % e) + + else: + # Resolve Setup integration + importlib.reload(utils) + utils.setup(env) + + return True diff --git a/pype/hosts/fusion/__init__.py b/pype/hosts/fusion/__init__.py index 7af75cebc8..61eaf44ddb 100644 --- a/pype/hosts/fusion/__init__.py +++ b/pype/hosts/fusion/__init__.py @@ -1,63 +1,38 @@ -import os +from .pipeline import ( + install, + uninstall, + publish, + launch_workfiles_app +) -from avalon import api as avalon -from pyblish import api as pyblish -from pype import PLUGINS_DIR - -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "fusion", "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "fusion", "inventory") +from .utils import ( + setup +) -def install(): - print("Registering Fusion plug-ins..") - pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - avalon.register_plugin_path(avalon.Creator, CREATE_PATH) - avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) +from .lib import ( + get_additional_data, + update_frame_range +) - pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) - - # Disable all families except for the ones we explicitly want to see - family_states = ["imagesequence", - "camera", - "pointcache"] - - avalon.data["familiesStateDefault"] = False - avalon.data["familiesStateToggled"] = family_states +from .menu import launch_pype_menu -def uninstall(): - print("Deregistering Fusion plug-ins..") - pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) +__all__ = [ + # pipeline + "install", + "uninstall", + "publish", + "launch_workfiles_app", - pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) + # utils + "setup", + "get_resolve_module", + # lib + "get_additional_data", + "update_frame_range", -def on_pyblish_instance_toggled(instance, new_value, old_value): - """Toggle saver tool passthrough states on instance toggles.""" - - from avalon.fusion import comp_lock_and_undo_chunk - - comp = instance.context.data.get("currentComp") - if not comp: - return - - savers = [tool for tool in instance if - getattr(tool, "ID", None) == "Saver"] - if not savers: - return - - # Whether instances should be passthrough based on new value - passthrough = not new_value - with comp_lock_and_undo_chunk(comp, - undo_queue_name="Change instance " - "active state"): - for tool in savers: - attrs = tool.GetAttrs() - current = attrs["TOOLB_PassThrough"] - if current != passthrough: - tool.SetAttrs({"TOOLB_PassThrough": passthrough}) + # menu + "launch_pype_menu", +] diff --git a/pype/hosts/fusion/menu.py b/pype/hosts/fusion/menu.py new file mode 100644 index 0000000000..251b3a8b4f --- /dev/null +++ b/pype/hosts/fusion/menu.py @@ -0,0 +1,170 @@ +import os +import sys + +from Qt import QtWidgets, QtCore + +from .pipeline import ( + publish, + launch_workfiles_app +) + +from avalon.tools import ( + creator, + loader, + sceneinventory, + libraryloader +) + +from .scripts import ( + set_rendermode, + duplicate_with_inputs +) + + +def load_stylesheet(): + path = os.path.join(os.path.dirname(__file__), "menu_style.qss") + if not os.path.exists(path): + print("Unable to load stylesheet, file not found in resources") + return "" + + with open(path, "r") as file_stream: + stylesheet = file_stream.read() + return stylesheet + + +class Spacer(QtWidgets.QWidget): + def __init__(self, height, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + self.setFixedHeight(height) + + real_spacer = QtWidgets.QWidget(self) + real_spacer.setObjectName("Spacer") + real_spacer.setFixedHeight(height) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(real_spacer) + + self.setLayout(layout) + + +class PypeMenu(QtWidgets.QWidget): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + + self.setObjectName("PypeMenu") + + self.setWindowFlags( + QtCore.Qt.Window + | QtCore.Qt.CustomizeWindowHint + | QtCore.Qt.WindowTitleHint + | QtCore.Qt.WindowCloseButtonHint + | QtCore.Qt.WindowStaysOnTopHint + ) + self.render_mode_widget = None + self.setWindowTitle("Pype") + workfiles_btn = QtWidgets.QPushButton("Workfiles", self) + create_btn = QtWidgets.QPushButton("Create", self) + publish_btn = QtWidgets.QPushButton("Publish", self) + load_btn = QtWidgets.QPushButton("Load", self) + inventory_btn = QtWidgets.QPushButton("Inventory", self) + libload_btn = QtWidgets.QPushButton("Library", self) + rendermode_btn = QtWidgets.QPushButton("Set render mode", self) + duplicate_with_inputs_btn = QtWidgets.QPushButton( + "Duplicate with input connections", self + ) + reset_resolution_btn = QtWidgets.QPushButton( + "Reset Resolution from project", self + ) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(10, 20, 10, 20) + + layout.addWidget(workfiles_btn) + layout.addWidget(create_btn) + layout.addWidget(publish_btn) + layout.addWidget(load_btn) + layout.addWidget(inventory_btn) + + layout.addWidget(Spacer(15, self)) + + layout.addWidget(libload_btn) + + layout.addWidget(Spacer(15, self)) + + layout.addWidget(rendermode_btn) + + layout.addWidget(Spacer(15, self)) + + layout.addWidget(duplicate_with_inputs_btn) + layout.addWidget(reset_resolution_btn) + + self.setLayout(layout) + + workfiles_btn.clicked.connect(self.on_workfile_clicked) + create_btn.clicked.connect(self.on_create_clicked) + publish_btn.clicked.connect(self.on_publish_clicked) + load_btn.clicked.connect(self.on_load_clicked) + inventory_btn.clicked.connect(self.on_inventory_clicked) + libload_btn.clicked.connect(self.on_libload_clicked) + rendermode_btn.clicked.connect(self.on_rendernode_clicked) + duplicate_with_inputs_btn.clicked.connect( + self.on_duplicate_with_inputs_clicked) + reset_resolution_btn.clicked.connect(self.on_reset_resolution_clicked) + + def on_workfile_clicked(self): + print("Clicked Workfile") + launch_workfiles_app() + + def on_create_clicked(self): + print("Clicked Create") + creator.show() + + def on_publish_clicked(self): + print("Clicked Publish") + publish(None) + + def on_load_clicked(self): + print("Clicked Load") + loader.show(use_context=True) + + def on_inventory_clicked(self): + print("Clicked Inventory") + sceneinventory.show() + + def on_libload_clicked(self): + print("Clicked Library") + libraryloader.show() + + def on_rendernode_clicked(self): + from avalon import style + print("Clicked Set Render Mode") + if self.render_mode_widget is None: + window = set_rendermode.SetRenderMode() + window.setStyleSheet(style.load_stylesheet()) + window.show() + self.render_mode_widget = window + else: + self.render_mode_widget.show() + + def on_duplicate_with_inputs_clicked(self): + duplicate_with_inputs.duplicate_with_input_connections() + print("Clicked Set Colorspace") + + def on_reset_resolution_clicked(self): + print("Clicked Reset Resolution") + + +def launch_pype_menu(): + app = QtWidgets.QApplication(sys.argv) + app.setQuitOnLastWindowClosed(False) + + pype_menu = PypeMenu() + + stylesheet = load_stylesheet() + pype_menu.setStyleSheet(stylesheet) + + pype_menu.show() + + sys.exit(app.exec_()) diff --git a/pype/hosts/fusion/menu_style.qss b/pype/hosts/fusion/menu_style.qss new file mode 100644 index 0000000000..df4fd7e949 --- /dev/null +++ b/pype/hosts/fusion/menu_style.qss @@ -0,0 +1,29 @@ +QWidget { + background-color: #282828; + border-radius: 3; +} + +QPushButton { + border: 1px solid #090909; + background-color: #201f1f; + color: #ffffff; + padding: 5; +} + +QPushButton:focus { + background-color: "#171717"; + color: #d0d0d0; +} + +QPushButton:hover { + background-color: "#171717"; + color: #e64b3d; +} + +#PypeMenu { + border: 1px solid #fef9ef; +} + +#Spacer { + background-color: #282828; +} diff --git a/pype/hosts/fusion/pipeline.py b/pype/hosts/fusion/pipeline.py new file mode 100644 index 0000000000..d593f2b615 --- /dev/null +++ b/pype/hosts/fusion/pipeline.py @@ -0,0 +1,114 @@ +""" +Basic avalon integration +""" +import os + +from avalon.tools import workfiles +from avalon import api as avalon +from pyblish import api as pyblish +from pypeapp import Logger +from pype import PLUGINS_DIR + +log = Logger().get_logger(__name__, "fusion") + + +AVALON_CONFIG = os.environ["AVALON_CONFIG"] + +LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "fusion", "inventory") + +PUBLISH_PATH = os.path.join( + PLUGINS_DIR, "fusion", "publish" +).replace("\\", "/") + + +def install(): + """Install fusion-specific functionality of avalon-core. + + This is where you install menus and register families, data + and loaders into fusion. + + It is called automatically when installing via `api.install(avalon.fusion)` + + See the Maya equivalent for inspiration on how to implement this. + + """ + + # Disable all families except for the ones we explicitly want to see + family_states = ["imagesequence", + "camera", + "pointcache"] + avalon.data["familiesStateDefault"] = False + avalon.data["familiesStateToggled"] = family_states + + log.info("pype.hosts.fusion installed") + + pyblish.register_host("fusion") + pyblish.register_plugin_path(PUBLISH_PATH) + log.info("Registering Fusion plug-ins..") + + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + + +def uninstall(): + """Uninstall all tha was installed + + This is where you undo everything that was done in `install()`. + That means, removing menus, deregistering families and data + and everything. It should be as though `install()` was never run, + because odds are calling this function means the user is interested + in re-installing shortly afterwards. If, for example, he has been + modifying the menu or registered families. + + """ + pyblish.deregister_host("fusion") + pyblish.deregister_plugin_path(PUBLISH_PATH) + log.info("Deregistering Fusion plug-ins..") + + avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) + avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + + pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) + + +def on_pyblish_instance_toggled(instance, new_value, old_value): + """Toggle saver tool passthrough states on instance toggles.""" + + from avalon.fusion import comp_lock_and_undo_chunk + + comp = instance.context.data.get("currentComp") + if not comp: + return + + savers = [tool for tool in instance if + getattr(tool, "ID", None) == "Saver"] + if not savers: + return + + # Whether instances should be passthrough based on new value + passthrough = not new_value + with comp_lock_and_undo_chunk(comp, + undo_queue_name="Change instance " + "active state"): + for tool in savers: + attrs = tool.GetAttrs() + current = attrs["TOOLB_PassThrough"] + if current != passthrough: + tool.SetAttrs({"TOOLB_PassThrough": passthrough}) + + +def launch_workfiles_app(*args): + workdir = os.environ["AVALON_WORKDIR"] + workfiles.show(workdir) + + +def publish(parent): + """Shorthand to publish from within host""" + from avalon.tools import publish + return publish.show(parent) diff --git a/pype/hosts/fusion/scripts/duplicate_with_inputs.py b/pype/hosts/fusion/scripts/duplicate_with_inputs.py new file mode 100644 index 0000000000..992dd2cd2d --- /dev/null +++ b/pype/hosts/fusion/scripts/duplicate_with_inputs.py @@ -0,0 +1,42 @@ +from avalon import fusion + + +def is_connected(input): + """Return whether an input has incoming connection""" + return input.GetAttrs()["INPB_Connected"] + + +def duplicate_with_input_connections(): + """Duplicate selected tools with incoming connections.""" + + comp = fusion.get_current_comp() + original_tools = comp.GetToolList(True).values() + if not original_tools: + return # nothing selected + + with fusion.comp_lock_and_undo_chunk( + comp, "Duplicate With Input Connections"): + + # Generate duplicates + comp.Copy() + comp.SetActiveTool() + comp.Paste() + duplicate_tools = comp.GetToolList(True).values() + + # Copy connections + for original, new in zip(original_tools, duplicate_tools): + + original_inputs = original.GetInputList().values() + new_inputs = new.GetInputList().values() + assert len(original_inputs) == len(new_inputs) + + for original_input, new_input in zip(original_inputs, new_inputs): + + if is_connected(original_input): + + if is_connected(new_input): + # Already connected if it is between the copied tools + continue + + new_input.ConnectTo(original_input.GetConnectedOutput()) + assert is_connected(new_input), "Must be connected now" diff --git a/pype/hosts/fusion/scripts/fusion_switch_shot.py b/pype/hosts/fusion/scripts/fusion_switch_shot.py index 4cb20c3a61..a3f2116db8 100644 --- a/pype/hosts/fusion/scripts/fusion_switch_shot.py +++ b/pype/hosts/fusion/scripts/fusion_switch_shot.py @@ -32,7 +32,7 @@ def _format_version_folder(folder): new_version = 1 if os.path.isdir(folder): - re_version = re.compile("v\d+$") + re_version = re.compile(r"v\d+$") versions = [i for i in os.listdir(folder) if os.path.isdir(i) and re_version.match(i)] if versions: @@ -87,7 +87,7 @@ def _format_filepath(session): # Create new unqiue filepath if os.path.exists(new_filepath): - new_filepath = studio.version_up(new_filepath) + new_filepath = pype.version_up(new_filepath) return new_filepath @@ -95,6 +95,15 @@ def _format_filepath(session): def _update_savers(comp, session): """Update all savers of the current comp to ensure the output is correct + This will refactor the Saver file outputs to the renders of the new session + that is provided. + + In the case the original saver path had a path set relative to a /fusion/ + folder then that relative path will be matched with the exception of all + "version" (e.g. v010) references will be reset to v001. Otherwise only a + version folder will be computed in the new session's work "render" folder + to dump the files in and keeping the original filenames. + Args: comp (object): current comp instance session (dict): the current Avalon session @@ -114,8 +123,36 @@ def _update_savers(comp, session): savers = comp.GetToolList(False, "Saver").values() for saver in savers: filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0] - filename = os.path.basename(filepath) - new_path = os.path.join(renders_version, filename) + + # Get old relative path to the "fusion" app folder so we can apply + # the same relative path afterwards. If not found fall back to + # using just a version folder with the filename in it. + # todo: can we make this less magical? + relpath = filepath.replace("\\", "/").rsplit("/fusion/", 1)[-1] + + if os.path.isabs(relpath): + # If not relative to a "/fusion/" folder then just use filename + filename = os.path.basename(filepath) + log.warning("Can't parse relative path, refactoring to only" + "filename in a version folder: %s" % filename) + new_path = os.path.join(renders_version, filename) + + else: + # Else reuse the relative path + # Reset version in folder and filename in the relative path + # to v001. The version should be is only detected when prefixed + # with either `_v` (underscore) or `/v` (folder) + version_pattern = r"(/|_)v[0-9]+" + if re.search(version_pattern, relpath): + new_relpath = re.sub(version_pattern, + r"\1v001", + relpath) + log.info("Resetting version folders to v001: " + "%s -> %s" % (relpath, new_relpath)) + relpath = new_relpath + + new_path = os.path.join(new_work, relpath) + saver["Clip"] = new_path @@ -138,6 +175,13 @@ def update_frame_range(comp, representations): versions = io.find({"type": "version", "_id": {"$in": version_ids}}) versions = list(versions) + versions = [v for v in versions + if v["data"].get("startFrame", None) is not None] + + if not versions: + log.warning("No versions loaded to match frame range to.\n") + return + start = min(v["data"]["frameStart"] for v in versions) end = max(v["data"]["frameEnd"] for v in versions) @@ -180,7 +224,8 @@ def switch(asset_name, filepath=None, new=True): else: fusion = _get_fusion_instance() current_comp = fusion.LoadComp(filepath, quiet=True) - assert current_comp is not None, "Fusion could not load '%s'" % filepath + assert current_comp is not None, ( + "Fusion could not load '{}'").format(filepath) host = api.registered_host() containers = list(host.ls()) @@ -189,8 +234,9 @@ def switch(asset_name, filepath=None, new=True): representations = [] for container in containers: try: - representation = pype.switch_item(container, - asset_name=asset_name) + representation = pype.switch_item( + container, + asset_name=asset_name) representations.append(representation) except Exception as e: current_comp.Print("Error in switching! %s\n" % e.message) @@ -223,6 +269,8 @@ def switch(asset_name, filepath=None, new=True): if __name__ == '__main__': + # QUESTION: can we convert this to gui rather then standalone script? + # TODO: convert to gui tool import argparse parser = argparse.ArgumentParser(description="Switch to a shot within an" diff --git a/pype/hosts/fusion/scripts/publish_filesequence.py b/pype/hosts/fusion/scripts/publish_filesequence.py deleted file mode 100644 index c37ceee07c..0000000000 --- a/pype/hosts/fusion/scripts/publish_filesequence.py +++ /dev/null @@ -1,87 +0,0 @@ -"""This module is used for command line publishing of image sequences.""" - -import os -import sys -import logging - -handler = logging.basicConfig() -log = logging.getLogger("Publish Image Sequences") -log.setLevel(logging.DEBUG) - -error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" - - -def publish(paths, gui=False): - """Publish rendered image sequences based on the job data - - Args: - paths (list): a list of paths where to publish from - gui (bool, Optional): Choose to show Pyblish GUI, default is False - - Returns: - None - - """ - - assert isinstance(paths, (list, tuple)), "Must be list of paths" - log.info(paths) - assert any(paths), "No paths found in the list" - # Set the paths to publish for the collector if any provided - if paths: - os.environ["FILESEQUENCE"] = os.pathsep.join(paths) - - # Install Avalon with shell as current host - from avalon import api, shell - api.install(shell) - - # Register target and host - import pyblish.api - pyblish.api.register_target("filesequence") - pyblish.api.register_host("shell") - - # Publish items - if gui: - import pyblish_qml - pyblish_qml.show(modal=True) - else: - - import pyblish.util - context = pyblish.util.publish() - - if not context: - log.warning("Nothing collected.") - sys.exit(1) - - # Collect errors, {plugin name: error} - error_results = [r for r in context.data["results"] if r["error"]] - - if error_results: - log.error(" Errors occurred ...") - for result in error_results: - log.error(error_format.format(**result)) - sys.exit(2) - - -def __main__(): - import argparse - parser = argparse.ArgumentParser() - parser.add_argument("--paths", - nargs="*", - default=[], - help="The filepaths to publish. This can be a " - "directory or a path to a .json publish " - "configuration.") - parser.add_argument("--gui", - default=False, - action="store_true", - help="Whether to run Pyblish in GUI mode.") - - kwargs, args = parser.parse_known_args() - - print("Running publish imagesequence...") - print("Paths: {}".format(kwargs.paths or [os.getcwd()])) - publish(kwargs.paths, gui=kwargs.gui) - - -if __name__ == '__main__': - __main__() diff --git a/pype/hosts/fusion/scripts/set_rendermode.py b/pype/hosts/fusion/scripts/set_rendermode.py new file mode 100644 index 0000000000..cb0b9da513 --- /dev/null +++ b/pype/hosts/fusion/scripts/set_rendermode.py @@ -0,0 +1,112 @@ +from avalon.vendor.Qt import QtWidgets +from avalon.vendor import qtawesome +import avalon.fusion as avalon + + +_help = {"local": "Render the comp on your own machine and publish " + "it from that the destination folder", + "farm": "Submit a Fusion render job to a Render farm to use all other" + " computers and add a publish job"} + + +class SetRenderMode(QtWidgets.QWidget): + + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent) + + self._comp = avalon.get_current_comp() + self._comp_name = self._get_comp_name() + + self.setWindowTitle("Set Render Mode") + self.setFixedSize(300, 175) + + layout = QtWidgets.QVBoxLayout() + + # region comp info + comp_info_layout = QtWidgets.QHBoxLayout() + + update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh", + color="white"), "") + update_btn.setFixedWidth(25) + update_btn.setFixedHeight(25) + + comp_information = QtWidgets.QLineEdit() + comp_information.setEnabled(False) + + comp_info_layout.addWidget(comp_information) + comp_info_layout.addWidget(update_btn) + # endregion comp info + + # region modes + mode_options = QtWidgets.QComboBox() + mode_options.addItems(_help.keys()) + + mode_information = QtWidgets.QTextEdit() + mode_information.setReadOnly(True) + # endregion modes + + accept_btn = QtWidgets.QPushButton("Accept") + + layout.addLayout(comp_info_layout) + layout.addWidget(mode_options) + layout.addWidget(mode_information) + layout.addWidget(accept_btn) + + self.setLayout(layout) + + self.comp_information = comp_information + self.update_btn = update_btn + + self.mode_options = mode_options + self.mode_information = mode_information + + self.accept_btn = accept_btn + + self.connections() + self.update() + + # Force updated render mode help text + self._update_rendermode_info() + + def connections(self): + """Build connections between code and buttons""" + + self.update_btn.clicked.connect(self.update) + self.accept_btn.clicked.connect(self._set_comp_rendermode) + self.mode_options.currentIndexChanged.connect( + self._update_rendermode_info) + + def update(self): + """Update all information in the UI""" + + self._comp = avalon.get_current_comp() + self._comp_name = self._get_comp_name() + self.comp_information.setText(self._comp_name) + + # Update current comp settings + mode = self._get_comp_rendermode() + index = self.mode_options.findText(mode) + self.mode_options.setCurrentIndex(index) + + def _update_rendermode_info(self): + rendermode = self.mode_options.currentText() + self.mode_information.setText(_help[rendermode]) + + def _get_comp_name(self): + return self._comp.GetAttrs("COMPS_Name") + + def _get_comp_rendermode(self): + return self._comp.GetData("pype.rendermode") or "local" + + def _set_comp_rendermode(self): + rendermode = self.mode_options.currentText() + self._comp.SetData("pype.rendermode", rendermode) + + self._comp.Print("Updated render mode to '%s'\n" % rendermode) + self.hide() + + def _validation(self): + ui_mode = self.mode_options.currentText() + comp_mode = self._get_comp_rendermode() + + return comp_mode == ui_mode diff --git a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py new file mode 100644 index 0000000000..90e08c4edb --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_selected_to32bit.py @@ -0,0 +1,15 @@ +from avalon.fusion import comp_lock_and_undo_chunk + +from avalon import fusion +comp = fusion.get_current_comp() + + +def main(): + """Set all selected backgrounds to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Selected Backgrounds to 32bit'): + tools = comp.GetToolList(True, "Background").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py new file mode 100644 index 0000000000..30ce36fee7 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/32bit/backgrounds_to32bit.py @@ -0,0 +1,14 @@ +from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion +comp = fusion.get_current_comp() + + +def main(): + """Set all backgrounds to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Backgrounds to 32bit'): + tools = comp.GetToolList(False, "Background").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py new file mode 100644 index 0000000000..403febf19b --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/32bit/loaders_selected_to32bit.py @@ -0,0 +1,14 @@ +from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion +comp = fusion.get_current_comp() + + +def main(): + """Set all selected loaders to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Selected Loaders to 32bit'): + tools = comp.GetToolList(True, "Loader").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py b/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py new file mode 100644 index 0000000000..e5670fe41b --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/32bit/loaders_to32bit.py @@ -0,0 +1,14 @@ +from avalon.fusion import comp_lock_and_undo_chunk +from avalon import fusion +comp = fusion.get_current_comp() + + +def main(): + """Set all loaders to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Loaders to 32bit'): + tools = comp.GetToolList(False, "Loader").values() + for tool in tools: + tool.Depth = 5 + + +main() diff --git a/pype/hosts/fusion/utility_scripts/Pype_menu.py b/pype/hosts/fusion/utility_scripts/Pype_menu.py new file mode 100644 index 0000000000..3d6a4607e1 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/Pype_menu.py @@ -0,0 +1,26 @@ +import os +import sys +import pype + +from pypeapp import Logger + +log = Logger().get_logger(__name__) + + +def main(env): + from pype.hosts.fusion import menu + import avalon.fusion + # Registers pype's Global pyblish plugins + pype.install() + + # activate resolve from pype + avalon.api.install(avalon.fusion) + + log.info(f"Avalon registred hosts: {avalon.api.registered_host()}") + + menu.launch_pype_menu() + + +if __name__ == "__main__": + result = main(os.environ) + sys.exit(not bool(result)) diff --git a/pype/hosts/fusion/utility_scripts/switch_ui.py b/pype/hosts/fusion/utility_scripts/switch_ui.py new file mode 100644 index 0000000000..e0b6b3f882 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/switch_ui.py @@ -0,0 +1,201 @@ +import os +import glob +import logging + +import avalon.io as io +import avalon.api as api +import avalon.pipeline as pipeline +import avalon.fusion +import avalon.style as style +from avalon.vendor.Qt import QtWidgets, QtCore +from avalon.vendor import qtawesome as qta + + +log = logging.getLogger("Fusion Switch Shot") + + +class App(QtWidgets.QWidget): + + def __init__(self, parent=None): + + ################################################ + # |---------------------| |------------------| # + # |Comp | |Asset | # + # |[..][ v]| |[ v]| # + # |---------------------| |------------------| # + # | Update existing comp [ ] | # + # |------------------------------------------| # + # | Switch | # + # |------------------------------------------| # + ################################################ + + QtWidgets.QWidget.__init__(self, parent) + + layout = QtWidgets.QVBoxLayout() + + # Comp related input + comp_hlayout = QtWidgets.QHBoxLayout() + comp_label = QtWidgets.QLabel("Comp file") + comp_label.setFixedWidth(50) + comp_box = QtWidgets.QComboBox() + + button_icon = qta.icon("fa.folder", color="white") + open_from_dir = QtWidgets.QPushButton() + open_from_dir.setIcon(button_icon) + + comp_box.setFixedHeight(25) + open_from_dir.setFixedWidth(25) + open_from_dir.setFixedHeight(25) + + comp_hlayout.addWidget(comp_label) + comp_hlayout.addWidget(comp_box) + comp_hlayout.addWidget(open_from_dir) + + # Asset related input + asset_hlayout = QtWidgets.QHBoxLayout() + asset_label = QtWidgets.QLabel("Shot") + asset_label.setFixedWidth(50) + + asset_box = QtWidgets.QComboBox() + asset_box.setLineEdit(QtWidgets.QLineEdit()) + asset_box.setFixedHeight(25) + + refresh_icon = qta.icon("fa.refresh", color="white") + refresh_btn = QtWidgets.QPushButton() + refresh_btn.setIcon(refresh_icon) + + asset_box.setFixedHeight(25) + refresh_btn.setFixedWidth(25) + refresh_btn.setFixedHeight(25) + + asset_hlayout.addWidget(asset_label) + asset_hlayout.addWidget(asset_box) + asset_hlayout.addWidget(refresh_btn) + + # Options + options = QtWidgets.QHBoxLayout() + options.setAlignment(QtCore.Qt.AlignLeft) + + current_comp_check = QtWidgets.QCheckBox() + current_comp_check.setChecked(True) + current_comp_label = QtWidgets.QLabel("Use current comp") + + options.addWidget(current_comp_label) + options.addWidget(current_comp_check) + + accept_btn = QtWidgets.QPushButton("Switch") + + layout.addLayout(options) + layout.addLayout(comp_hlayout) + layout.addLayout(asset_hlayout) + layout.addWidget(accept_btn) + + self._open_from_dir = open_from_dir + self._comps = comp_box + self._assets = asset_box + self._use_current = current_comp_check + self._accept_btn = accept_btn + self._refresh_btn = refresh_btn + + self.setWindowTitle("Fusion Switch Shot") + self.setLayout(layout) + + self.resize(260, 140) + self.setMinimumWidth(260) + self.setFixedHeight(140) + + self.connections() + + # Update ui to correct state + self._on_use_current_comp() + self._refresh() + + def connections(self): + self._use_current.clicked.connect(self._on_use_current_comp) + self._open_from_dir.clicked.connect(self._on_open_from_dir) + self._refresh_btn.clicked.connect(self._refresh) + self._accept_btn.clicked.connect(self._on_switch) + + def _on_use_current_comp(self): + state = self._use_current.isChecked() + self._open_from_dir.setEnabled(not state) + self._comps.setEnabled(not state) + + def _on_open_from_dir(self): + + start_dir = self._get_context_directory() + comp_file, _ = QtWidgets.QFileDialog.getOpenFileName( + self, "Choose comp", start_dir) + + if not comp_file: + return + + # Create completer + self.populate_comp_box([comp_file]) + self._refresh() + + def _refresh(self): + # Clear any existing items + self._assets.clear() + + asset_names = [a["name"] for a in self.collect_assets()] + completer = QtWidgets.QCompleter(asset_names) + + self._assets.setCompleter(completer) + self._assets.addItems(asset_names) + + def _on_switch(self): + + if not self._use_current.isChecked(): + file_name = self._comps.itemData(self._comps.currentIndex()) + else: + comp = avalon.fusion.get_current_comp() + file_name = comp.GetAttrs("COMPS_FileName") + + asset = self._assets.currentText() + + import colorbleed.scripts.fusion_switch_shot as switch_shot + switch_shot.switch(asset_name=asset, filepath=file_name, new=True) + + def _get_context_directory(self): + + project = io.find_one({"type": "project", + "name": api.Session["AVALON_PROJECT"]}, + projection={"config": True}) + + template = project["config"]["template"]["work"] + dir = pipeline._format_work_template(template, api.Session) + + return dir + + def collect_slap_comps(self, directory): + items = glob.glob("{}/*.comp".format(directory)) + return items + + def collect_assets(self): + return list(io.find({"type": "asset", "silo": "film"})) + + def populate_comp_box(self, files): + """Ensure we display the filename only but the path is stored as well + + Args: + files (list): list of full file path [path/to/item/item.ext,] + + Returns: + None + """ + + for f in files: + filename = os.path.basename(f) + self._comps.addItem(filename, userData=f) + + +if __name__ == '__main__': + import sys + api.install(avalon.fusion) + + app = QtWidgets.QApplication(sys.argv) + window = App() + window.setStyleSheet(style.load_stylesheet()) + window.show() + sys.exit(app.exec_()) diff --git a/pype/hosts/fusion/utility_scripts/update_loader_ranges.py b/pype/hosts/fusion/utility_scripts/update_loader_ranges.py new file mode 100644 index 0000000000..9ddf1e6dc6 --- /dev/null +++ b/pype/hosts/fusion/utility_scripts/update_loader_ranges.py @@ -0,0 +1,37 @@ +"""Forces Fusion to 'retrigger' the Loader to update. + +Warning: + This might change settings like 'Reverse', 'Loop', trims and other + settings of the Loader. So use this at your own risk. + +""" +from avalon import fusion + + +def update_loader_ranges(): + comp = fusion.get_current_comp() + with fusion.comp_lock_and_undo_chunk(comp, "Reload clip time ranges"): + tools = comp.GetToolList(True, "Loader").values() + for tool in tools: + + # Get tool attributes + tool_a = tool.GetAttrs() + clipTable = tool_a['TOOLST_Clip_Name'] + altclipTable = tool_a['TOOLST_AltClip_Name'] + startTime = tool_a['TOOLNT_Clip_Start'] + old_global_in = tool.GlobalIn[comp.CurrentTime] + + # Reapply + for index, _ in clipTable.items(): + time = startTime[index] + tool.Clip[time] = tool.Clip[time] + + for index, _ in altclipTable.items(): + time = startTime[index] + tool.ProxyFilename[time] = tool.ProxyFilename[time] + + tool.GlobalIn[comp.CurrentTime] = old_global_in + + +if __name__ == '__main__': + update_loader_ranges() diff --git a/pype/hosts/fusion/utils.py b/pype/hosts/fusion/utils.py new file mode 100644 index 0000000000..40e28f6473 --- /dev/null +++ b/pype/hosts/fusion/utils.py @@ -0,0 +1,85 @@ +#! python3 + +""" +Fusion tools for setting environment +""" + +import os +import shutil + +from pypeapp import Logger + +log = Logger().get_logger(__name__, "fusion") + + +def _sync_utility_scripts(env=None): + """ Synchronizing basic utlility scripts for resolve. + + To be able to run scripts from inside `Fusion/Workspace/Scripts` menu + all scripts has to be accessible from defined folder. + """ + if not env: + env = os.environ + + # initiate inputs + scripts = {} + us_env = env.get("FUSION_UTILITY_SCRIPTS_SOURCE_DIR") + us_dir = env.get("FUSION_UTILITY_SCRIPTS_DIR", "") + us_paths = [os.path.join( + os.path.dirname(__file__), + "utility_scripts" + )] + + # collect script dirs + if us_env: + log.info(f"Utility Scripts Env: `{us_env}`") + us_paths = us_env.split( + os.pathsep) + us_paths + + # collect scripts from dirs + for path in us_paths: + scripts.update({path: os.listdir(path)}) + + log.info(f"Utility Scripts Dir: `{us_paths}`") + log.info(f"Utility Scripts: `{scripts}`") + + # make sure no script file is in folder + if next((s for s in os.listdir(us_dir)), None): + for s in os.listdir(us_dir): + path = os.path.normpath( + os.path.join(us_dir, s)) + log.info(f"Removing `{path}`...") + + # remove file or directory if not in our folders + if not os.path.isdir(path): + os.remove(path) + else: + shutil.rmtree(path) + + # copy scripts into Resolve's utility scripts dir + for d, sl in scripts.items(): + # directory and scripts list + for s in sl: + # script in script list + src = os.path.normpath(os.path.join(d, s)) + dst = os.path.normpath(os.path.join(us_dir, s)) + + log.info(f"Copying `{src}` to `{dst}`...") + + # copy file or directory from our folders to fusion's folder + if not os.path.isdir(src): + shutil.copy2(src, dst) + else: + shutil.copytree(src, dst) + + +def setup(env=None): + """ Wrapper installer started from pype.hooks.fusion.FusionPrelaunch() + """ + if not env: + env = os.environ + + # synchronize resolve utility scripts + _sync_utility_scripts(env) + + log.info("Fusion Pype wrapper has been installed") diff --git a/pype/hosts/nukestudio/__init__.py b/pype/hosts/hiero/__init__.py similarity index 79% rename from pype/hosts/nukestudio/__init__.py rename to pype/hosts/hiero/__init__.py index c84b288f4a..50fd39440b 100644 --- a/pype/hosts/nukestudio/__init__.py +++ b/pype/hosts/hiero/__init__.py @@ -31,17 +31,17 @@ __all__ = [ ] # get logger -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") ''' Creating all important host related variables ''' AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") # plugin root path -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "inventory") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "hiero", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "hiero", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "hiero", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "hiero", "inventory") # registering particular pyblish gui but `lite` is recomended!! if os.getenv("PYBLISH_GUI", None): @@ -50,7 +50,7 @@ if os.getenv("PYBLISH_GUI", None): def install(): """ - Installing Nukestudio integration for avalon + Installing Hiero integration for avalon Args: config (obj): avalon config module `pype` in our case, it is not @@ -61,8 +61,8 @@ def install(): # adding all events _register_events() - log.info("Registering NukeStudio plug-ins..") - pyblish.register_host("nukestudio") + log.info("Registering Hiero plug-ins..") + pyblish.register_host("hiero") pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) @@ -87,11 +87,11 @@ def install(): def uninstall(): """ - Uninstalling Nukestudio integration for avalon + Uninstalling Hiero integration for avalon """ - log.info("Deregistering NukeStudio plug-ins..") - pyblish.deregister_host("nukestudio") + log.info("Deregistering Hiero plug-ins..") + pyblish.deregister_host("hiero") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) @@ -102,7 +102,7 @@ def _register_events(): Adding all callbacks. """ - # if task changed then change notext of nukestudio + # if task changed then change notext of hiero avalon.on("taskChanged", _update_menu_task_label) log.info("Installed event callback for 'taskChanged'..") diff --git a/pype/hosts/nukestudio/events.py b/pype/hosts/hiero/events.py similarity index 98% rename from pype/hosts/nukestudio/events.py rename to pype/hosts/hiero/events.py index 509319f717..d78f8d54d4 100644 --- a/pype/hosts/nukestudio/events.py +++ b/pype/hosts/hiero/events.py @@ -4,7 +4,7 @@ from pype.api import Logger from .lib import sync_avalon_data_to_workfile, launch_workfiles_app from .tags import add_tags_from_presets -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") def startupCompleted(event): diff --git a/pype/hosts/nukestudio/lib.py b/pype/hosts/hiero/lib.py similarity index 98% rename from pype/hosts/nukestudio/lib.py rename to pype/hosts/hiero/lib.py index 6f2d9ad357..db7199a190 100644 --- a/pype/hosts/nukestudio/lib.py +++ b/pype/hosts/hiero/lib.py @@ -8,7 +8,7 @@ from avalon.vendor.Qt import (QtWidgets, QtGui) import pype.api as pype from pype.api import Logger, Anatomy -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") cached_process = None @@ -82,7 +82,7 @@ def sync_avalon_data_to_workfile(): def launch_workfiles_app(event): """ - Event for launching workfiles after nukestudio start + Event for launching workfiles after hiero start Args: event (obj): required but unused @@ -109,9 +109,9 @@ def reload_config(): "pypeapp", "{}.api".format(AVALON_CONFIG), "{}.templates".format(AVALON_CONFIG), - "{}.hosts.nukestudio.lib".format(AVALON_CONFIG), - "{}.hosts.nukestudio.menu".format(AVALON_CONFIG), - "{}.hosts.nukestudio.tags".format(AVALON_CONFIG) + "{}.hosts.hiero.lib".format(AVALON_CONFIG), + "{}.hosts.hiero.menu".format(AVALON_CONFIG), + "{}.hosts.hiero.tags".format(AVALON_CONFIG) ): log.info("Reloading module: {}...".format(module)) try: diff --git a/pype/hosts/nukestudio/menu.py b/pype/hosts/hiero/menu.py similarity index 97% rename from pype/hosts/nukestudio/menu.py rename to pype/hosts/hiero/menu.py index 35adcfc16c..697381f3cb 100644 --- a/pype/hosts/nukestudio/menu.py +++ b/pype/hosts/hiero/menu.py @@ -12,7 +12,7 @@ from .lib import ( set_workfiles ) -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") self = sys.modules[__name__] self._change_context_menu = None @@ -38,7 +38,7 @@ def _update_menu_task_label(*args): def install(): """ - Installing menu into Nukestudio + Installing menu into Hiero """ diff --git a/pype/hosts/nukestudio/tags.json b/pype/hosts/hiero/tags.json similarity index 100% rename from pype/hosts/nukestudio/tags.json rename to pype/hosts/hiero/tags.json diff --git a/pype/hosts/nukestudio/tags.py b/pype/hosts/hiero/tags.py similarity index 93% rename from pype/hosts/nukestudio/tags.py rename to pype/hosts/hiero/tags.py index c2b1d0d728..551dc1698d 100644 --- a/pype/hosts/nukestudio/tags.py +++ b/pype/hosts/hiero/tags.py @@ -3,10 +3,12 @@ import os import json import hiero +from pprint import pformat + from pype.api import Logger from avalon import io -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") def tag_data(): @@ -65,21 +67,23 @@ def add_tags_from_presets(): log.debug("Setting default tags on project: {}".format(project.name())) - # get nukestudio tags.json + # get hiero tags.json nks_pres_tags = tag_data() # Get project task types. tasks = io.find_one({"type": "project"})["config"]["tasks"] nks_pres_tags["[Tasks]"] = {} - for task in tasks: - nks_pres_tags["[Tasks]"][task["name"]] = { + log.debug("__ tasks: {}".format(pformat(tasks))) + for task_type in tasks.keys(): + nks_pres_tags["[Tasks]"][task_type.lower()] = { "editable": "1", "note": "", "icon": { "path": "icons:TagGood.png" }, "metadata": { - "family": "task" + "family": "task", + "type": task_type } } diff --git a/pype/hosts/nukestudio/workio.py b/pype/hosts/hiero/workio.py similarity index 95% rename from pype/hosts/nukestudio/workio.py rename to pype/hosts/hiero/workio.py index 2cf898aa33..f11a34c9a8 100644 --- a/pype/hosts/nukestudio/workio.py +++ b/pype/hosts/hiero/workio.py @@ -4,11 +4,11 @@ from avalon import api from pype.api import Logger -log = Logger().get_logger(__name__, "nukestudio") +log = Logger().get_logger(__name__, "hiero") def file_extensions(): - return api.HOST_WORKFILE_EXTENSIONS["nukestudio"] + return api.HOST_WORKFILE_EXTENSIONS["hiero"] def has_unsaved_changes(): diff --git a/pype/hosts/maya/menu.py b/pype/hosts/maya/menu.py index 70ad8d31ca..9dadd8d1f5 100644 --- a/pype/hosts/maya/menu.py +++ b/pype/hosts/maya/menu.py @@ -32,8 +32,19 @@ def deferred(): command=lambda *args: BuildWorkfile().process() ) + def add_look_assigner_item(): + import mayalookassigner + cmds.menuItem( + "Look assigner", + parent=pipeline._menu, + command=lambda *args: mayalookassigner.show() + ) + log.info("Attempting to install scripts menu..") + add_build_workfiles_item() + add_look_assigner_item() + try: import scriptsmenu.launchformaya as launchformaya import scriptsmenu.scriptsmenu as scriptsmenu @@ -42,7 +53,6 @@ def deferred(): "Skipping studio.menu install, because " "'scriptsmenu' module seems unavailable." ) - add_build_workfiles_item() return # load configuration of custom menu diff --git a/pype/modules/clockify/launcher_actions/ClockifySync.py b/pype/modules/clockify/launcher_actions/ClockifySync.py index a77c038076..422a346023 100644 --- a/pype/modules/clockify/launcher_actions/ClockifySync.py +++ b/pype/modules/clockify/launcher_actions/ClockifySync.py @@ -30,7 +30,7 @@ class ClockifySync(api.Action): projects_info = {} for project in projects_to_sync: - task_types = [task['name'] for task in project['config']['tasks']] + task_types = project['config']['tasks'].keys() projects_info[project['name']] = task_types clockify_projects = self.clockapi.get_projects() diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 292ce752cf..28114c7fdc 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -16,6 +16,7 @@ from bson.objectid import ObjectId from bson.errors import InvalidId from pymongo import UpdateOne import ftrack_api +from pype.api import config log = Logger().get_logger(__name__) @@ -23,9 +24,9 @@ log = Logger().get_logger(__name__) # Current schemas for avalon types EntitySchemas = { - "project": "avalon-core:project-2.0", + "project": "avalon-core:project-2.1", "asset": "avalon-core:asset-3.0", - "config": "avalon-core:config-1.0" + "config": "avalon-core:config-1.1" } # Group name of custom attributes @@ -50,7 +51,7 @@ def check_regex(name, entity_type, in_schema=None, schema_patterns=None): if in_schema: schema_name = in_schema elif entity_type == "project": - schema_name = "project-2.0" + schema_name = "project-2.1" elif entity_type == "task": schema_name = "task" @@ -103,6 +104,14 @@ def get_pype_attr(session, split_hierarchical=True): def from_dict_to_set(data): + """ + Converts 'data' into $set part of MongoDB update command. + Args: + data: (dictionary) - up-to-date data from Ftrack + + Returns: + (dictionary) - { "$set" : "{..}"} + """ result = {"$set": {}} dict_queue = queue.Queue() dict_queue.put((None, data)) @@ -114,7 +123,8 @@ def from_dict_to_set(data): if _key is not None: new_key = "{}.{}".format(_key, key) - if not isinstance(value, dict): + if not isinstance(value, dict) or \ + (isinstance(value, dict) and not bool(value)): # empty dic result["$set"][new_key] = value continue dict_queue.put((new_key, value)) @@ -123,6 +133,8 @@ def from_dict_to_set(data): def get_avalon_project_template(project_name): """Get avalon template + Args: + project_name: (string) Returns: dictionary with templates """ @@ -135,6 +147,16 @@ def get_avalon_project_template(project_name): def get_project_apps(in_app_list): + """ + Returns metadata information about apps in 'in_app_list' enhanced + from toml files. + Args: + in_app_list: (list) - names of applications + + Returns: + tuple (list, dictionary) - list of dictionaries about apps + dictionary of warnings + """ apps = [] # TODO report missing_toml_msg = "Missing config file for application" @@ -239,6 +261,28 @@ def get_hierarchical_attributes(session, entity, attr_names, attr_defaults={}): return hier_values +def get_task_short_name(task_type): + """ + Returns short name (code) for 'task_type'. Short name stored in + metadata dictionary in project.config per each 'task_type'. + Could be used in anatomy, paths etc. + If no appropriate short name is found in mapping, 'task_type' is + returned back unchanged. + + Currently stores data in: + 'pype-config/presets/ftrack/project_defaults.json' + Args: + task_type: (string) - Animation | Modeling ... + + Returns: + (string) - anim | model ... + """ + presets = config.get_presets()['ftrack']['project_defaults']\ + .get("task_short_names") + + return presets.get(task_type, task_type) + + class SyncEntitiesFactory: dbcon = AvalonMongoDB() @@ -378,7 +422,7 @@ class SyncEntitiesFactory: "custom_attributes": {}, "hier_attrs": {}, "avalon_attrs": {}, - "tasks": [] + "tasks": {} }) for entity in all_project_entities: @@ -389,7 +433,9 @@ class SyncEntitiesFactory: continue elif entity_type_low == "task": - entities_dict[parent_id]["tasks"].append(entity["name"]) + # enrich task info with additional metadata + task = {"type": entity["type"]["name"]} + entities_dict[parent_id]["tasks"][entity["name"]] = task continue entity_id = entity["id"] @@ -416,6 +462,13 @@ class SyncEntitiesFactory: @property def avalon_ents_by_id(self): + """ + Returns dictionary of avalon tracked entities (assets stored in + MongoDB) accessible by its '_id' + (mongo intenal ID - example ObjectId("5f48de5830a9467b34b69798")) + Returns: + (dictionary) - {"(_id)": whole entity asset} + """ if self._avalon_ents_by_id is None: self._avalon_ents_by_id = {} for entity in self.avalon_entities: @@ -425,6 +478,14 @@ class SyncEntitiesFactory: @property def avalon_ents_by_ftrack_id(self): + """ + Returns dictionary of Mongo ids of avalon tracked entities + (assets stored in MongoDB) accessible by its 'ftrackId' + (id from ftrack) + (example '431ee3f2-e91a-11ea-bfa4-92591a5b5e3e') + Returns: + (dictionary) - {"(ftrackId)": "_id"} + """ if self._avalon_ents_by_ftrack_id is None: self._avalon_ents_by_ftrack_id = {} for entity in self.avalon_entities: @@ -437,6 +498,13 @@ class SyncEntitiesFactory: @property def avalon_ents_by_name(self): + """ + Returns dictionary of Mongo ids of avalon tracked entities + (assets stored in MongoDB) accessible by its 'name' + (example 'Hero') + Returns: + (dictionary) - {"(name)": "_id"} + """ if self._avalon_ents_by_name is None: self._avalon_ents_by_name = {} for entity in self.avalon_entities: @@ -446,6 +514,15 @@ class SyncEntitiesFactory: @property def avalon_ents_by_parent_id(self): + """ + Returns dictionary of avalon tracked entities + (assets stored in MongoDB) accessible by its 'visualParent' + (example ObjectId("5f48de5830a9467b34b69798")) + + Fills 'self._avalon_archived_ents' for performance + Returns: + (dictionary) - {"(_id)": whole entity} + """ if self._avalon_ents_by_parent_id is None: self._avalon_ents_by_parent_id = collections.defaultdict(list) for entity in self.avalon_entities: @@ -458,6 +535,14 @@ class SyncEntitiesFactory: @property def avalon_archived_ents(self): + """ + Returns list of archived assets from DB + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_ents' for performance + Returns: + (list) of assets + """ if self._avalon_archived_ents is None: self._avalon_archived_ents = [ ent for ent in self.dbcon.find({"type": "archived_asset"}) @@ -466,6 +551,14 @@ class SyncEntitiesFactory: @property def avalon_archived_by_name(self): + """ + Returns list of archived assets from DB + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_by_name' for performance + Returns: + (dictionary of lists) of assets accessible by asset name + """ if self._avalon_archived_by_name is None: self._avalon_archived_by_name = collections.defaultdict(list) for ent in self.avalon_archived_ents: @@ -474,6 +567,14 @@ class SyncEntitiesFactory: @property def avalon_archived_by_id(self): + """ + Returns dictionary of archived assets from DB + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_by_id' for performance + Returns: + (dictionary) of assets accessible by asset mongo _id + """ if self._avalon_archived_by_id is None: self._avalon_archived_by_id = { str(ent["_id"]): ent for ent in self.avalon_archived_ents @@ -482,6 +583,15 @@ class SyncEntitiesFactory: @property def avalon_archived_by_parent_id(self): + """ + Returns dictionary of archived assets from DB per their's parent + (their "type" == 'archived_asset') + + Fills 'self._avalon_archived_by_parent_id' for performance + Returns: + (dictionary of lists) of assets accessible by asset parent + mongo _id + """ if self._avalon_archived_by_parent_id is None: self._avalon_archived_by_parent_id = collections.defaultdict(list) for entity in self.avalon_archived_ents: @@ -494,6 +604,14 @@ class SyncEntitiesFactory: @property def subsets_by_parent_id(self): + """ + Returns dictionary of subsets from Mongo ("type": "subset") + grouped by their parent. + + Fills 'self._subsets_by_parent_id' for performance + Returns: + (dictionary of lists) + """ if self._subsets_by_parent_id is None: self._subsets_by_parent_id = collections.defaultdict(list) for subset in self.dbcon.find({"type": "subset"}): @@ -515,6 +633,11 @@ class SyncEntitiesFactory: @property def all_ftrack_names(self): + """ + Returns lists of names of all entities in Ftrack + Returns: + (list) + """ return [ ent_dict["name"] for ent_dict in self.entities_dict.values() if ( ent_dict.get("name") @@ -534,8 +657,9 @@ class SyncEntitiesFactory: name = entity_dict["name"] entity_type = entity_dict["entity_type"] # Tasks must be checked too - for task_name in entity_dict["tasks"]: - passed = task_names.get(task_name) + for task in entity_dict["tasks"].items(): + task_name, task = task + passed = task_name if passed is None: passed = check_regex( task_name, "task", schema_patterns=_schema_patterns @@ -1014,9 +1138,13 @@ class SyncEntitiesFactory: if not msg or not items: continue self.report_items["warning"][msg] = items - + tasks = {} + for tt in task_types: + tasks[tt["name"]] = { + "short_name": get_task_short_name(tt["name"]) + } self.entities_dict[id]["final_entity"]["config"] = { - "tasks": [{"name": tt["name"]} for tt in task_types], + "tasks": tasks, "apps": proj_apps } continue @@ -1029,7 +1157,7 @@ class SyncEntitiesFactory: data["parents"] = parents data["hierarchy"] = hierarchy - data["tasks"] = self.entities_dict[id].pop("tasks", []) + data["tasks"] = self.entities_dict[id].pop("tasks", {}) self.entities_dict[id]["final_entity"]["data"] = data self.entities_dict[id]["final_entity"]["type"] = "asset" @@ -1904,10 +2032,10 @@ class SyncEntitiesFactory: filter = {"_id": ObjectId(mongo_id)} change_data = from_dict_to_set(changes) mongo_changes_bulk.append(UpdateOne(filter, change_data)) - if not mongo_changes_bulk: # TODO LOG return + log.debug("mongo_changes_bulk:: {}".format(mongo_changes_bulk)) self.dbcon.bulk_write(mongo_changes_bulk) def reload_parents(self, hierarchy_changing_ids): diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 7d4e0333d6..2ee0898711 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -143,15 +143,17 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): # existing_tasks.append(child['type']['name']) for task in tasks: - if task.lower() in existing_tasks: + task_name = next(iter(task)) + task_type = task[task_name]["type"] + if task_name.lower() in existing_tasks: print("Task {} already exists".format(task)) continue - tasks_to_create.append(task) + tasks_to_create.append((task_name, task_type)) - for task in tasks_to_create: + for task_name, task_type in tasks_to_create: self.create_task( - name=task, - task_type=task, + name=task_name, + task_type=task_type, parent=entity ) try: diff --git a/pype/plugins/fusion/create/create_tiff_saver.py b/pype/plugins/fusion/create/create_exr_saver.py similarity index 83% rename from pype/plugins/fusion/create/create_tiff_saver.py rename to pype/plugins/fusion/create/create_exr_saver.py index 4911650ed2..d5092d1d03 100644 --- a/pype/plugins/fusion/create/create_tiff_saver.py +++ b/pype/plugins/fusion/create/create_exr_saver.py @@ -4,16 +4,16 @@ import avalon.api from avalon import fusion -class CreateTiffSaver(avalon.api.Creator): +class CreateOpenEXRSaver(avalon.api.Creator): - name = "tiffDefault" - label = "Create Tiff Saver" + name = "openexrDefault" + label = "Create OpenEXR Saver" hosts = ["fusion"] - family = "saver" + family = "render" def process(self): - file_format = "TiffFormat" + file_format = "OpenEXRFormat" comp = fusion.get_current_comp() @@ -23,7 +23,7 @@ class CreateTiffSaver(avalon.api.Creator): workdir = os.path.normpath(os.environ["AVALON_WORKDIR"]) filename = "{}..tiff".format(self.name) - filepath = os.path.join(workdir, "render", "preview", filename) + filepath = os.path.join(workdir, "render", filename) with fusion.comp_lock_and_undo_chunk(comp): args = (-32768, -32768) # Magical position numbers @@ -43,4 +43,3 @@ class CreateTiffSaver(avalon.api.Creator): # Set file format attributes saver[file_format]["Depth"] = 1 # int8 | int16 | float32 | other saver[file_format]["SaveAlpha"] = 0 - diff --git a/pype/plugins/fusion/load/load_sequence.py b/pype/plugins/fusion/load/load_sequence.py index ce6bca6c77..24d48fb9da 100644 --- a/pype/plugins/fusion/load/load_sequence.py +++ b/pype/plugins/fusion/load/load_sequence.py @@ -4,6 +4,10 @@ import contextlib from avalon import api import avalon.io as io +from avalon import fusion + +comp = fusion.get_current_comp() + @contextlib.contextmanager def preserve_inputs(tool, inputs): @@ -113,7 +117,7 @@ def loader_shift(loader, frame, relative=True): class FusionLoadSequence(api.Loader): """Load image sequence into Fusion""" - families = ["imagesequence"] + families = ["imagesequence", "review"] representations = ["*"] label = "Load sequence" @@ -134,7 +138,7 @@ class FusionLoadSequence(api.Loader): namespace = context['asset']['name'] # Use the first file for now - path = self._get_first_image(self.fname) + path = self._get_first_image(os.path.dirname(self.fname)) # Create the Loader with the filename path set comp = get_current_comp() diff --git a/pype/plugins/fusion/publish/collect_instances.py b/pype/plugins/fusion/publish/collect_instances.py index 6dbb1b1a97..3c7224e65c 100644 --- a/pype/plugins/fusion/publish/collect_instances.py +++ b/pype/plugins/fusion/publish/collect_instances.py @@ -43,8 +43,8 @@ class CollectInstances(pyblish.api.ContextPlugin): savers = [tool for tool in tools if tool.ID == "Saver"] start, end = get_comp_render_range(comp) - context.data["frameStart"] = start - context.data["frameEnd"] = end + context.data["frameStart"] = int(start) + context.data["frameEnd"] = int(end) for tool in savers: path = tool["Clip"][comp.TIME_UNDEFINED] @@ -76,8 +76,11 @@ class CollectInstances(pyblish.api.ContextPlugin): "outputDir": os.path.dirname(path), "ext": ext, # todo: should be redundant "label": label, - "families": ["saver"], - "family": "saver", + "frameStart": context.data["frameStart"], + "frameEnd": context.data["frameEnd"], + "fps": context.data["fps"], + "families": ["render", "review", "ftrack"], + "family": "render", "active": active, "publish": active # backwards compatibility }) diff --git a/pype/plugins/fusion/publish/collect_render_target.py b/pype/plugins/fusion/publish/collect_render_target.py index b6217f1ddf..50cc4fd3e9 100644 --- a/pype/plugins/fusion/publish/collect_render_target.py +++ b/pype/plugins/fusion/publish/collect_render_target.py @@ -5,8 +5,8 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): """Collect current comp's render Mode Options: - renderlocal - deadline + local + farm Note that this value is set for each comp separately. When you save the comp this information will be stored in that file. If for some reason the @@ -23,22 +23,22 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.4 label = "Collect Render Mode" hosts = ["fusion"] - families = ["saver"] + families = ["render"] def process(self, instance): """Collect all image sequence tools""" - options = ["renderlocal", "deadline"] + options = ["local", "farm"] comp = instance.context.data.get("currentComp") if not comp: raise RuntimeError("No comp previously collected, unable to " "retrieve Fusion version.") - rendermode = comp.GetData("pype.rendermode") or "renderlocal" + rendermode = comp.GetData("pype.rendermode") or "local" assert rendermode in options, "Must be supported render mode" self.log.info("Render mode: {0}".format(rendermode)) # Append family - family = "saver.{0}".format(rendermode) + family = "render.{0}".format(rendermode) instance.data["families"].append(family) diff --git a/pype/plugins/fusion/publish/increment_current_file_deadline.py b/pype/plugins/fusion/publish/increment_current_file_deadline.py index 6545d84da3..9641ba7ef6 100644 --- a/pype/plugins/fusion/publish/increment_current_file_deadline.py +++ b/pype/plugins/fusion/publish/increment_current_file_deadline.py @@ -11,7 +11,7 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["fusion"] - families = ["saver.deadline"] + families = ["render.farm"] optional = True def process(self, context): @@ -23,7 +23,7 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): if any(plugin.__name__ == "FusionSubmitDeadline" for plugin in errored_plugins): raise RuntimeError("Skipping incrementing current file because " - "submission to deadline failed.") + "submission to render farm failed.") comp = context.data.get("currentComp") assert comp, "Must have comp" diff --git a/pype/plugins/fusion/publish/publish_image_sequences.py b/pype/plugins/fusion/publish/publish_image_sequences.py deleted file mode 100644 index 9fe9ddc4cb..0000000000 --- a/pype/plugins/fusion/publish/publish_image_sequences.py +++ /dev/null @@ -1,98 +0,0 @@ -import re -import os -import json -import subprocess - -import pyblish.api - -from pype.action import get_errored_plugins_from_data - - -def _get_script(): - """Get path to the image sequence script""" - - # todo: use a more elegant way to get the python script - - try: - from pype.scripts import publish_filesequence - except Exception: - raise RuntimeError("Expected module 'publish_imagesequence'" - "to be available") - - module_path = publish_filesequence.__file__ - if module_path.endswith(".pyc"): - module_path = module_path[:-len(".pyc")] + ".py" - - return module_path - - -class PublishImageSequence(pyblish.api.InstancePlugin): - """Publish the generated local image sequences.""" - - order = pyblish.api.IntegratorOrder - label = "Publish Rendered Image Sequence(s)" - hosts = ["fusion"] - families = ["saver.renderlocal"] - - def process(self, instance): - - # Skip this plug-in if the ExtractImageSequence failed - errored_plugins = get_errored_plugins_from_data(instance.context) - if any(plugin.__name__ == "FusionRenderLocal" for plugin in - errored_plugins): - raise RuntimeError("Fusion local render failed, " - "publishing images skipped.") - - subset = instance.data["subset"] - ext = instance.data["ext"] - - # Regex to match resulting renders - regex = "^{subset}.*[0-9]+{ext}+$".format(subset=re.escape(subset), - ext=re.escape(ext)) - - # The instance has most of the information already stored - metadata = { - "regex": regex, - "frameStart": instance.context.data["frameStart"], - "frameEnd": instance.context.data["frameEnd"], - "families": ["imagesequence"], - } - - # Write metadata and store the path in the instance - output_directory = instance.data["outputDir"] - path = os.path.join(output_directory, - "{}_metadata.json".format(subset)) - with open(path, "w") as f: - json.dump(metadata, f) - - assert os.path.isfile(path), ("Stored path is not a file for %s" - % instance.data["name"]) - - # Suppress any subprocess console - startupinfo = subprocess.STARTUPINFO() - startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - startupinfo.wShowWindow = subprocess.SW_HIDE - - process = subprocess.Popen(["python", _get_script(), - "--paths", path], - bufsize=1, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - startupinfo=startupinfo) - - while True: - output = process.stdout.readline() - # Break when there is no output or a return code has been given - if output == '' and process.poll() is not None: - process.stdout.close() - break - if output: - line = output.strip() - if line.startswith("ERROR"): - self.log.error(line) - else: - self.log.info(line) - - if process.returncode != 0: - raise RuntimeError("Process quit with non-zero " - "return code: {}".format(process.returncode)) diff --git a/pype/plugins/fusion/publish/render_local.py b/pype/plugins/fusion/publish/render_local.py index c97fe1a13d..19449ead67 100644 --- a/pype/plugins/fusion/publish/render_local.py +++ b/pype/plugins/fusion/publish/render_local.py @@ -1,9 +1,11 @@ +import os import pyblish.api import avalon.fusion as fusion +from pprint import pformat -class FusionRenderLocal(pyblish.api.InstancePlugin): +class Fusionlocal(pyblish.api.InstancePlugin): """Render the current Fusion composition locally. Extract the result of savers by starting a comp render @@ -11,15 +13,13 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): """ - order = pyblish.api.ExtractorOrder + order = pyblish.api.ExtractorOrder - 0.1 label = "Render Local" hosts = ["fusion"] - families = ["saver.renderlocal"] + families = ["render.local"] def process(self, instance): - # This should be a ContextPlugin, but this is a workaround - # for a bug in pyblish to run once for a family: issue #250 context = instance.context key = "__hasRun{}".format(self.__class__.__name__) if context.data.get(key, False): @@ -28,15 +28,40 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): context.data[key] = True current_comp = context.data["currentComp"] - start_frame = current_comp.GetAttrs("COMPN_RenderStart") - end_frame = current_comp.GetAttrs("COMPN_RenderEnd") + frame_start = current_comp.GetAttrs("COMPN_RenderStart") + frame_end = current_comp.GetAttrs("COMPN_RenderEnd") + path = instance.data["path"] + output_dir = instance.data["outputDir"] + + ext = os.path.splitext(os.path.basename(path))[-1] self.log.info("Starting render") - self.log.info("Start frame: {}".format(start_frame)) - self.log.info("End frame: {}".format(end_frame)) + self.log.info("Start frame: {}".format(frame_start)) + self.log.info("End frame: {}".format(frame_end)) with fusion.comp_lock_and_undo_chunk(current_comp): result = current_comp.Render() + if "representations" not in instance.data: + instance.data["representations"] = [] + + collected_frames = os.listdir(output_dir) + repre = { + 'name': ext[1:], + 'ext': ext[1:], + 'frameStart': "%0{}d".format(len(str(frame_end))) % frame_start, + 'files': collected_frames, + "stagingDir": output_dir, + } + instance.data["representations"].append(repre) + + # review representation + repre_preview = repre.copy() + repre_preview["name"] = repre_preview["ext"] = "mp4" + repre_preview["tags"] = ["review", "preview", "ftrackreview", "delete"] + instance.data["representations"].append(repre_preview) + + self.log.debug(f"_ instance.data: {pformat(instance.data)}") + if not result: raise RuntimeError("Comp render failed") diff --git a/pype/plugins/fusion/publish/save_scene.py b/pype/plugins/fusion/publish/save_scene.py index 850ac5c372..0cdfafa095 100644 --- a/pype/plugins/fusion/publish/save_scene.py +++ b/pype/plugins/fusion/publish/save_scene.py @@ -7,7 +7,7 @@ class FusionSaveComp(pyblish.api.ContextPlugin): label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["fusion"] - families = ["saver"] + families = ["render"] def process(self, context): diff --git a/pype/plugins/fusion/publish/submit_deadline.py b/pype/plugins/fusion/publish/submit_deadline.py index 0dd34ba713..ed3fb06586 100644 --- a/pype/plugins/fusion/publish/submit_deadline.py +++ b/pype/plugins/fusion/publish/submit_deadline.py @@ -19,10 +19,9 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder hosts = ["fusion"] - families = ["saver.deadline"] + families = ["render.farm"] def process(self, instance): - instance.data["toBeRenderedOn"] = "deadline" context = instance.context key = "__hasRun{}".format(self.__class__.__name__) diff --git a/pype/plugins/fusion/publish/validate_background_depth.py b/pype/plugins/fusion/publish/validate_background_depth.py index 88a52ad52d..de042ae315 100644 --- a/pype/plugins/fusion/publish/validate_background_depth.py +++ b/pype/plugins/fusion/publish/validate_background_depth.py @@ -10,7 +10,7 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): label = "Validate Background Depth 32 bit" actions = [action.RepairAction] hosts = ["fusion"] - families = ["saver"] + families = ["render"] optional = True @classmethod diff --git a/pype/plugins/fusion/publish/validate_comp_saved.py b/pype/plugins/fusion/publish/validate_comp_saved.py index 425168fbdf..cabe65af6e 100644 --- a/pype/plugins/fusion/publish/validate_comp_saved.py +++ b/pype/plugins/fusion/publish/validate_comp_saved.py @@ -8,7 +8,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["saver"] + families = ["render"] hosts = ["fusion"] def process(self, context): diff --git a/pype/plugins/fusion/publish/validate_create_folder_checked.py b/pype/plugins/fusion/publish/validate_create_folder_checked.py index 00a8526c6b..cce3695c31 100644 --- a/pype/plugins/fusion/publish/validate_create_folder_checked.py +++ b/pype/plugins/fusion/publish/validate_create_folder_checked.py @@ -13,7 +13,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder actions = [action.RepairAction] label = "Validate Create Folder Checked" - families = ["saver"] + families = ["render"] hosts = ["fusion"] @classmethod diff --git a/pype/plugins/fusion/publish/validate_filename_has_extension.py b/pype/plugins/fusion/publish/validate_filename_has_extension.py index d3762ad290..4795a2aa05 100644 --- a/pype/plugins/fusion/publish/validate_filename_has_extension.py +++ b/pype/plugins/fusion/publish/validate_filename_has_extension.py @@ -14,7 +14,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Filename Has Extension" - families = ["saver"] + families = ["render"] hosts = ["fusion"] def process(self, instance): diff --git a/pype/plugins/fusion/publish/validate_saver_has_input.py b/pype/plugins/fusion/publish/validate_saver_has_input.py index 6887a9704c..7243b44a3e 100644 --- a/pype/plugins/fusion/publish/validate_saver_has_input.py +++ b/pype/plugins/fusion/publish/validate_saver_has_input.py @@ -10,7 +10,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Has Input" - families = ["saver"] + families = ["render"] hosts = ["fusion"] @classmethod diff --git a/pype/plugins/fusion/publish/validate_saver_passthrough.py b/pype/plugins/fusion/publish/validate_saver_passthrough.py index 2da5cf2494..aed3835de3 100644 --- a/pype/plugins/fusion/publish/validate_saver_passthrough.py +++ b/pype/plugins/fusion/publish/validate_saver_passthrough.py @@ -6,7 +6,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Passthrough" - families = ["saver"] + families = ["render"] hosts = ["fusion"] def process(self, context): diff --git a/pype/plugins/fusion/publish/validate_unique_subsets.py b/pype/plugins/fusion/publish/validate_unique_subsets.py index 2000e1c05d..b218a311ba 100644 --- a/pype/plugins/fusion/publish/validate_unique_subsets.py +++ b/pype/plugins/fusion/publish/validate_unique_subsets.py @@ -6,7 +6,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["saver"] + families = ["render"] hosts = ["fusion"] @classmethod @@ -14,7 +14,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): context = instance.context subset = instance.data["subset"] - for other_instance in context[:]: + for other_instance in context: if other_instance == instance: continue diff --git a/pype/plugins/global/publish/collect_avalon_entities.py b/pype/plugins/global/publish/collect_avalon_entities.py index 917172d40c..0b6423818e 100644 --- a/pype/plugins/global/publish/collect_avalon_entities.py +++ b/pype/plugins/global/publish/collect_avalon_entities.py @@ -86,3 +86,5 @@ class CollectAvalonEntities(pyblish.api.ContextPlugin): frame_end_h = frame_end + context.data["handleEnd"] context.data["frameStartHandle"] = frame_start_h context.data["frameEndHandle"] = frame_end_h + + context.data["fps"] = data["fps"] diff --git a/pype/plugins/global/publish/collect_current_pype_user.py b/pype/plugins/global/publish/collect_current_pype_user.py index 359e6b852c..a8947dd8fb 100644 --- a/pype/plugins/global/publish/collect_current_pype_user.py +++ b/pype/plugins/global/publish/collect_current_pype_user.py @@ -13,7 +13,7 @@ class CollectCurrentUserPype(pyblish.api.ContextPlugin): def process(self, context): user = os.getenv("PYPE_USERNAME", "").strip() if not user: - return + user = context.data.get("user", getpass.getuser()) context.data["user"] = user - self.log.debug("Pype user is \"{}\"".format(user)) + self.log.debug("Colected user \"{}\"".format(user)) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 6e8da1b054..353f2f27f0 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -23,10 +23,11 @@ class ExtractBurnin(pype.api.Extractor): "nuke", "maya", "shell", - "nukestudio", + "hiero", "premiere", "standalonepublisher", "harmony" + "fusion" ] optional = True @@ -314,12 +315,15 @@ class ExtractBurnin(pype.api.Extractor): "comment": context.data.get("comment") or "" }) - intent_label = context.data.get("intent") + intent_label = context.data.get("intent") or "" if intent_label and isinstance(intent_label, dict): - intent_label = intent_label.get("label") + value = intent_label.get("value") + if value: + intent_label = intent_label["label"] + else: + intent_label = "" - if intent_label: - burnin_data["intent"] = intent_label + burnin_data["intent"] = intent_label temp_data = { "frame_start": frame_start, diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 5d11eae058..64df672709 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -59,7 +59,7 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): data["inputs"] = entity_data.get("inputs", []) # Tasks. - tasks = entity_data.get("tasks", []) + tasks = entity_data.get("tasks", {}) if tasks is not None or len(tasks) > 0: data["tasks"] = tasks parents = [] @@ -99,11 +99,14 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if entity: # Do not override data, only update cur_entity_data = entity.get("data") or {} - new_tasks = data.pop("tasks", []) - if "tasks" in cur_entity_data and new_tasks: - for task_name in new_tasks: - if task_name not in cur_entity_data["tasks"]: - cur_entity_data["tasks"].append(task_name) + new_tasks = data.pop("tasks", {}) + if "tasks" not in cur_entity_data and not new_tasks: + continue + for task in new_tasks: + task_name = next(iter(task)) + if task_name in cur_entity_data["tasks"].keys(): + continue + cur_entity_data["tasks"][task_name] = task[task_name] cur_entity_data.update(data) data = cur_entity_data else: diff --git a/pype/plugins/global/publish/extract_jpeg.py b/pype/plugins/global/publish/extract_jpeg.py index d23ce4360f..dd822e9d56 100644 --- a/pype/plugins/global/publish/extract_jpeg.py +++ b/pype/plugins/global/publish/extract_jpeg.py @@ -9,7 +9,7 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin): """Create jpg thumbnail from sequence using ffmpeg""" label = "Extract Jpeg EXR" - hosts = ["shell"] + hosts = ["shell", "fusion"] order = pyblish.api.ExtractorOrder families = ["imagesequence", "render", "render2d", "source"] enabled = False diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 318c843b80..f4a39a7c31 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -26,10 +26,11 @@ class ExtractReview(pyblish.api.InstancePlugin): "nuke", "maya", "shell", - "nukestudio", + "hiero", "premiere", "harmony", - "standalonepublisher" + "standalonepublisher", + "fusion" ] # Supported extensions diff --git a/pype/plugins/global/publish/extract_scanline_exr.py b/pype/plugins/global/publish/extract_scanline_exr.py new file mode 100644 index 0000000000..ca62476ab2 --- /dev/null +++ b/pype/plugins/global/publish/extract_scanline_exr.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +"""Convert exrs in representation to tiled exrs usin oiio tools.""" +import os +import shutil + +import pyblish.api +import pype.api +import pype.lib + + +class ExtractScanlineExr(pyblish.api.InstancePlugin): + """Convert tiled EXRs to scanline using OIIO tool.""" + + label = "Extract Scanline EXR" + hosts = ["shell"] + order = pyblish.api.ExtractorOrder + families = ["imagesequence", "render", "render2d", "source"] + + def process(self, instance): + """Plugin entry point.""" + # get representation and loop them + representations = instance.data["representations"] + + representations_new = [] + + for repre in representations: + self.log.info( + "Processing representation {}".format(repre.get("name"))) + tags = repre.get("tags", []) + if "toScanline" not in tags: + self.log.info(" - missing toScanline tag") + continue + + # run only on exrs + if repre.get("ext") != "exr": + self.log.info("- not EXR files") + continue + + if not isinstance(repre['files'], (list, tuple)): + input_files = [repre['files']] + self.log.info("We have a single frame") + else: + input_files = repre['files'] + self.log.info("We have a sequence") + + stagingdir = os.path.normpath(repre.get("stagingDir")) + + oiio_tool_path = os.getenv("PYPE_OIIO_PATH", "") + + for file in input_files: + + original_name = os.path.join(stagingdir, file) + temp_name = os.path.join(stagingdir, "__{}".format(file)) + # move original render to temp location + shutil.move(original_name, temp_name) + oiio_cmd = [] + oiio_cmd.append(oiio_tool_path) + oiio_cmd.append( + os.path.join(stagingdir, temp_name) + ) + oiio_cmd.append("--scanline") + oiio_cmd.append("-o") + oiio_cmd.append(os.path.join(stagingdir, original_name)) + + subprocess_exr = " ".join(oiio_cmd) + self.log.info(f"running: {subprocess_exr}") + pype.api.subprocess(subprocess_exr) + + # raise error if there is no ouptput + if not os.path.exists(os.path.join(stagingdir, original_name)): + self.log.error( + ("File {} was not converted " + "by oiio tool!").format(original_name)) + raise AssertionError("OIIO tool conversion failed") + else: + try: + os.remove(temp_name) + except OSError as e: + self.log.warning("Unable to delete temp file") + self.log.warning(e) + + repre['name'] = 'exr' + try: + repre['tags'].remove('toScanline') + except ValueError: + # no `toScanline` tag present + pass + + instance.data["representations"] += representations_new diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 68549e9186..4dc6006076 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -521,8 +521,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # get 'files' info for representation and all attached resources self.log.debug("Preparing files information ...") representation["files"] = self.get_files_info( - instance, - self.integrated_file_sizes) + instance, + self.integrated_file_sizes) self.log.debug("__ representation: {}".format(representation)) destination_list.append(dst) @@ -543,10 +543,9 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): repre_ids_to_remove.append(repre["_id"]) io.delete_many({"_id": {"$in": repre_ids_to_remove}}) - self.log.debug("__ representations: {}".format(representations)) for rep in instance.data["representations"]: - self.log.debug("__ represNAME: {}".format(rep['name'])) - self.log.debug("__ represPATH: {}".format(rep['published_path'])) + self.log.debug("__ rep: {}".format(rep)) + io.insert_many(representations) instance.data["published_representations"] = ( published_representations diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 99f0ae7cb6..fd109cf881 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -174,7 +174,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_SERVER", "PYPE_METADATA_FILE", "AVALON_PROJECT", - "PYPE_LOG_NO_COLORS" + "PYPE_LOG_NO_COLORS", + "PYPE_USERNAME" ] # custom deadline atributes @@ -193,7 +194,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "slate": ["slateFrame"], "review": ["lutPath"], "render2d": ["bakeScriptPath", "bakeRenderPath", - "bakeWriteNodeName", "version"] + "bakeWriteNodeName", "version"], + "renderlayer": ["convertToScanline"] } # list of family names to transfer to new family if present @@ -297,6 +299,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): environment["PYPE_METADATA_FILE"] = roothless_metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] environment["PYPE_LOG_NO_COLORS"] = "1" + environment["PYPE_USERNAME"] = instance.context.data["user"] try: environment["PYPE_PYTHON_EXE"] = os.environ["PYPE_PYTHON_EXE"] except KeyError: @@ -491,6 +494,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "tags": ["review"] if preview else [] } + # support conversion from tiled to scanline + if instance_data.get("convertToScanline"): + self.log.info("Adding scanline conversion.") + rep["tags"].append("toScanline") + # poor man exclusion if ext in self.skip_integration_repre_list: rep["tags"].append("delete") @@ -581,6 +589,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if instance.get("multipartExr", False): rep["tags"].append("multipartExr") + # support conversion from tiled to scanline + if instance.get("convertToScanline"): + self.log.info("Adding scanline conversion.") + rep["tags"].append("toScanline") + representations.append(rep) self._solve_families(instance, preview) @@ -727,6 +740,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "family": "prerender", "families": []}) + # skip locking version if we are creating v01 + instance_version = instance.data.get("version") + if instance_version != 1: + instance_skeleton_data["version"] = instance_version + # transfer specific families from original instance to new render for item in self.families_transfer: if item in instance.data.get("families", []): diff --git a/pype/plugins/global/publish/validate_custom_ftrack_attributes.py b/pype/plugins/global/publish/validate_custom_ftrack_attributes.py index 633deaa6d1..4bddcd2e03 100644 --- a/pype/plugins/global/publish/validate_custom_ftrack_attributes.py +++ b/pype/plugins/global/publish/validate_custom_ftrack_attributes.py @@ -46,7 +46,7 @@ class ValidateFtrackAttributes(pyblish.api.InstancePlugin): "houdini", "maya", "nuke", - "nukestudio", + "hiero", "photoshop", "premiere", "resolve", diff --git a/pype/plugins/nukestudio/_unused/collect_metadata.py b/pype/plugins/hiero/_unused/collect_metadata.py similarity index 97% rename from pype/plugins/nukestudio/_unused/collect_metadata.py rename to pype/plugins/hiero/_unused/collect_metadata.py index 23d36ba4a2..c85cb4e898 100644 --- a/pype/plugins/nukestudio/_unused/collect_metadata.py +++ b/pype/plugins/hiero/_unused/collect_metadata.py @@ -6,7 +6,7 @@ class CollectClipMetadata(api.InstancePlugin): order = api.CollectorOrder + 0.01 label = "Collect Metadata" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): item = instance.data["item"] diff --git a/pype/plugins/nukestudio/_unused/collect_submission.py b/pype/plugins/hiero/_unused/collect_submission.py similarity index 100% rename from pype/plugins/nukestudio/_unused/collect_submission.py rename to pype/plugins/hiero/_unused/collect_submission.py diff --git a/pype/plugins/nukestudio/_unused/collect_timecodes.py b/pype/plugins/hiero/_unused/collect_timecodes.py similarity index 99% rename from pype/plugins/nukestudio/_unused/collect_timecodes.py rename to pype/plugins/hiero/_unused/collect_timecodes.py index 5ac07314a4..e79ee27a15 100644 --- a/pype/plugins/nukestudio/_unused/collect_timecodes.py +++ b/pype/plugins/hiero/_unused/collect_timecodes.py @@ -10,7 +10,7 @@ class CollectClipTimecodes(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.101 label = "Collect Timecodes" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): diff --git a/pype/plugins/nukestudio/_unused/collect_workfile_version.py b/pype/plugins/hiero/_unused/collect_workfile_version.py similarity index 100% rename from pype/plugins/nukestudio/_unused/collect_workfile_version.py rename to pype/plugins/hiero/_unused/collect_workfile_version.py diff --git a/pype/plugins/nukestudio/_unused/extract_plates_waiting.py b/pype/plugins/hiero/_unused/extract_plates_waiting.py similarity index 96% rename from pype/plugins/nukestudio/_unused/extract_plates_waiting.py rename to pype/plugins/hiero/_unused/extract_plates_waiting.py index 9a4d883917..f385f74a3e 100644 --- a/pype/plugins/nukestudio/_unused/extract_plates_waiting.py +++ b/pype/plugins/hiero/_unused/extract_plates_waiting.py @@ -8,7 +8,7 @@ class ExtractPlateCheck(api.ContextPlugin): order = api.ExtractorOrder + 0.01 label = "Plates Export Waiting" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["encode"] def process(self, context): diff --git a/pype/plugins/nukestudio/_unused/extract_tasks.py b/pype/plugins/hiero/_unused/extract_tasks.py similarity index 99% rename from pype/plugins/nukestudio/_unused/extract_tasks.py rename to pype/plugins/hiero/_unused/extract_tasks.py index 3e6ef9b71c..4f75728468 100644 --- a/pype/plugins/nukestudio/_unused/extract_tasks.py +++ b/pype/plugins/hiero/_unused/extract_tasks.py @@ -6,7 +6,7 @@ class ExtractTasks(api.InstancePlugin): order = api.ExtractorOrder label = "Tasks" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] optional = True diff --git a/pype/plugins/nukestudio/_unused/subset-representations_logic.txt b/pype/plugins/hiero/_unused/subset-representations_logic.txt similarity index 100% rename from pype/plugins/nukestudio/_unused/subset-representations_logic.txt rename to pype/plugins/hiero/_unused/subset-representations_logic.txt diff --git a/pype/plugins/nukestudio/_unused/validate_projectroot.py b/pype/plugins/hiero/_unused/validate_projectroot.py similarity index 97% rename from pype/plugins/nukestudio/_unused/validate_projectroot.py rename to pype/plugins/hiero/_unused/validate_projectroot.py index 94315014c6..51e5082250 100644 --- a/pype/plugins/nukestudio/_unused/validate_projectroot.py +++ b/pype/plugins/hiero/_unused/validate_projectroot.py @@ -22,7 +22,7 @@ class ValidateProjectRoot(api.ContextPlugin): order = api.ValidatorOrder label = "Project Root" - hosts = ["nukestudio"] + hosts = ["hiero"] actions = [RepairProjectRoot] def process(self, context): diff --git a/pype/plugins/nukestudio/_unused/validate_resolved_paths.py b/pype/plugins/hiero/_unused/validate_resolved_paths.py similarity index 96% rename from pype/plugins/nukestudio/_unused/validate_resolved_paths.py rename to pype/plugins/hiero/_unused/validate_resolved_paths.py index f1f0b7bbc8..21883aa1d3 100644 --- a/pype/plugins/nukestudio/_unused/validate_resolved_paths.py +++ b/pype/plugins/hiero/_unused/validate_resolved_paths.py @@ -5,7 +5,7 @@ class ValidateResolvedPaths(api.ContextPlugin): order = api.ValidatorOrder label = "Resolved Paths" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): import os diff --git a/pype/plugins/nukestudio/_unused/validate_task.py b/pype/plugins/hiero/_unused/validate_task.py similarity index 97% rename from pype/plugins/nukestudio/_unused/validate_task.py rename to pype/plugins/hiero/_unused/validate_task.py index ff8fa6b6e1..3af94273bb 100644 --- a/pype/plugins/nukestudio/_unused/validate_task.py +++ b/pype/plugins/hiero/_unused/validate_task.py @@ -13,7 +13,7 @@ class ValidateOutputRange(api.InstancePlugin): order = api.ValidatorOrder families = ["trackItem.task"] label = "Output Range" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, instance): @@ -43,7 +43,7 @@ class ValidateImageSequence(api.InstancePlugin): families = ["trackItem.task", "img"] match = api.Subset label = "Image Sequence" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, instance): diff --git a/pype/plugins/nukestudio/_unused/validate_track_item.py b/pype/plugins/hiero/_unused/validate_track_item.py similarity index 98% rename from pype/plugins/nukestudio/_unused/validate_track_item.py rename to pype/plugins/hiero/_unused/validate_track_item.py index 48f63b5608..f29e1d5d75 100644 --- a/pype/plugins/nukestudio/_unused/validate_track_item.py +++ b/pype/plugins/hiero/_unused/validate_track_item.py @@ -10,7 +10,7 @@ class ValidateClip(api.InstancePlugin): families = ["clip"] # match = api.Exact label = "Validate Track Item" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, instance): diff --git a/pype/plugins/nukestudio/_unused/validate_viewer_lut.py b/pype/plugins/hiero/_unused/validate_viewer_lut.py similarity index 53% rename from pype/plugins/nukestudio/_unused/validate_viewer_lut.py rename to pype/plugins/hiero/_unused/validate_viewer_lut.py index 08c084880d..4d307b5a71 100644 --- a/pype/plugins/nukestudio/_unused/validate_viewer_lut.py +++ b/pype/plugins/hiero/_unused/validate_viewer_lut.py @@ -2,11 +2,11 @@ from pyblish import api class ValidateViewerLut(api.ContextPlugin): - """Validate viewer lut in NukeStudio is the same as in Nuke.""" + """Validate viewer lut in Hiero is the same as in Nuke.""" order = api.ValidatorOrder label = "Viewer LUT" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True def process(self, context): @@ -14,8 +14,8 @@ class ValidateViewerLut(api.ContextPlugin): import hiero # nuke_lut = nuke.ViewerProcess.node()["current"].value() - nukestudio_lut = context.data["activeProject"].lutSettingViewer() - self.log.info("__ nukestudio_lut: {}".format(nukestudio_lut)) + hiero_lut = context.data["activeProject"].lutSettingViewer() + self.log.info("__ hiero_lut: {}".format(hiero_lut)) msg = "Viewer LUT can only be RGB" - assert "RGB" in nukestudio_lut, msg + assert "RGB" in hiero_lut, msg diff --git a/pype/plugins/nukestudio/load/load_sequences_to_timeline_asset_origin.py b/pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py similarity index 97% rename from pype/plugins/nukestudio/load/load_sequences_to_timeline_asset_origin.py rename to pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py index c56dcbcaaa..3c58a0a90d 100644 --- a/pype/plugins/nukestudio/load/load_sequences_to_timeline_asset_origin.py +++ b/pype/plugins/hiero/load/load_sequences_to_timeline_asset_origin.py @@ -1,6 +1,6 @@ from avalon import api import hiero -from pype.hosts.nukestudio import lib +from pype.hosts.hiero import lib reload(lib) diff --git a/pype/plugins/nukestudio/publish/collect_active_project.py b/pype/plugins/hiero/publish/collect_active_project.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_active_project.py rename to pype/plugins/hiero/publish/collect_active_project.py diff --git a/pype/plugins/nukestudio/publish/collect_assetbuilds.py b/pype/plugins/hiero/publish/collect_assetbuilds.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_assetbuilds.py rename to pype/plugins/hiero/publish/collect_assetbuilds.py index 76326c320b..f20acaf3da 100644 --- a/pype/plugins/nukestudio/publish/collect_assetbuilds.py +++ b/pype/plugins/hiero/publish/collect_assetbuilds.py @@ -14,7 +14,7 @@ class CollectAssetBuilds(api.ContextPlugin): # Run just after CollectClip order = api.CollectorOrder + 0.02 label = "Collect AssetBuilds" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): asset_builds = {} diff --git a/pype/plugins/nukestudio/publish/collect_audio.py b/pype/plugins/hiero/publish/collect_audio.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_audio.py rename to pype/plugins/hiero/publish/collect_audio.py index 727d7da795..a63a18be05 100644 --- a/pype/plugins/nukestudio/publish/collect_audio.py +++ b/pype/plugins/hiero/publish/collect_audio.py @@ -14,7 +14,7 @@ class CollectAudio(api.InstancePlugin): # Run just before CollectSubsets order = api.CollectorOrder + 0.1021 label = "Collect Audio" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_calculate_retime.py b/pype/plugins/hiero/publish/collect_calculate_retime.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_calculate_retime.py rename to pype/plugins/hiero/publish/collect_calculate_retime.py index a97b43a4ce..1b2f047da2 100644 --- a/pype/plugins/nukestudio/publish/collect_calculate_retime.py +++ b/pype/plugins/hiero/publish/collect_calculate_retime.py @@ -8,7 +8,7 @@ class CollectCalculateRetime(api.InstancePlugin): order = api.CollectorOrder + 0.02 label = "Collect Calculate Retiming" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['retime'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_clip_resolution.py b/pype/plugins/hiero/publish/collect_clip_resolution.py similarity index 96% rename from pype/plugins/nukestudio/publish/collect_clip_resolution.py rename to pype/plugins/hiero/publish/collect_clip_resolution.py index b70f8f2f95..8d441959ba 100644 --- a/pype/plugins/nukestudio/publish/collect_clip_resolution.py +++ b/pype/plugins/hiero/publish/collect_clip_resolution.py @@ -6,7 +6,7 @@ class CollectClipResolution(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.101 label = "Collect Clip Resoluton" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): sequence = instance.context.data['activeSequence'] diff --git a/pype/plugins/nukestudio/publish/collect_clips.py b/pype/plugins/hiero/publish/collect_clips.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_clips.py rename to pype/plugins/hiero/publish/collect_clips.py index d39e25bfc6..e11ad93883 100644 --- a/pype/plugins/nukestudio/publish/collect_clips.py +++ b/pype/plugins/hiero/publish/collect_clips.py @@ -9,7 +9,7 @@ class CollectClips(api.ContextPlugin): order = api.CollectorOrder + 0.01 label = "Collect Clips" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): # create asset_names conversion table diff --git a/pype/plugins/nukestudio/publish/collect_colorspace.py b/pype/plugins/hiero/publish/collect_colorspace.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_colorspace.py rename to pype/plugins/hiero/publish/collect_colorspace.py diff --git a/pype/plugins/nukestudio/publish/collect_current_file.py b/pype/plugins/hiero/publish/collect_current_file.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_current_file.py rename to pype/plugins/hiero/publish/collect_current_file.py diff --git a/pype/plugins/nukestudio/publish/collect_effects.py b/pype/plugins/hiero/publish/collect_effects.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_effects.py rename to pype/plugins/hiero/publish/collect_effects.py diff --git a/pype/plugins/nukestudio/publish/collect_frame_ranges.py b/pype/plugins/hiero/publish/collect_frame_ranges.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_frame_ranges.py rename to pype/plugins/hiero/publish/collect_frame_ranges.py index 1cb5e5dd1e..19a46d80b1 100644 --- a/pype/plugins/nukestudio/publish/collect_frame_ranges.py +++ b/pype/plugins/hiero/publish/collect_frame_ranges.py @@ -6,7 +6,7 @@ class CollectClipFrameRanges(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.101 label = "Collect Frame Ranges" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_framerate.py b/pype/plugins/hiero/publish/collect_framerate.py similarity index 95% rename from pype/plugins/nukestudio/publish/collect_framerate.py rename to pype/plugins/hiero/publish/collect_framerate.py index 694052f802..6d2d2eef2b 100644 --- a/pype/plugins/nukestudio/publish/collect_framerate.py +++ b/pype/plugins/hiero/publish/collect_framerate.py @@ -6,7 +6,7 @@ class CollectFramerate(api.ContextPlugin): order = api.CollectorOrder + 0.01 label = "Collect Framerate" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): sequence = context.data["activeSequence"] diff --git a/pype/plugins/nukestudio/publish/collect_handles.py b/pype/plugins/hiero/publish/collect_handles.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_handles.py rename to pype/plugins/hiero/publish/collect_handles.py index c16f1a5803..1f278a6b4c 100644 --- a/pype/plugins/nukestudio/publish/collect_handles.py +++ b/pype/plugins/hiero/publish/collect_handles.py @@ -6,7 +6,7 @@ class CollectClipHandles(api.ContextPlugin): order = api.CollectorOrder + 0.0121 label = "Collect Handles" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): assets_shared = context.data.get("assetsShared") diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/hiero/publish/collect_hierarchy_context.py similarity index 99% rename from pype/plugins/nukestudio/publish/collect_hierarchy_context.py rename to pype/plugins/hiero/publish/collect_hierarchy_context.py index 930efd618e..f18783cd37 100644 --- a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py +++ b/pype/plugins/hiero/publish/collect_hierarchy_context.py @@ -13,7 +13,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): """ label = "Collect Hierarchy Clip" - order = pyblish.api.CollectorOrder + 0.101 + order = pyblish.api.CollectorOrder + 0.102 families = ["clip"] def convert_to_entity(self, key, value): @@ -46,7 +46,7 @@ class CollectHierarchyInstance(pyblish.api.ContextPlugin): clip_out = instance.data["clipOut"] fps = context.data["fps"] - # build data for inner nukestudio project property + # build data for inner hiero project property data = { "sequence": ( context.data['activeSequence'].name().replace(' ', '_') diff --git a/pype/plugins/nukestudio/publish/collect_host.py b/pype/plugins/hiero/publish/collect_host.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_host.py rename to pype/plugins/hiero/publish/collect_host.py diff --git a/pype/plugins/nukestudio/publish/collect_host_version.py b/pype/plugins/hiero/publish/collect_host_version.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_host_version.py rename to pype/plugins/hiero/publish/collect_host_version.py diff --git a/pype/plugins/nukestudio/publish/collect_instance_version.py b/pype/plugins/hiero/publish/collect_instance_version.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_instance_version.py rename to pype/plugins/hiero/publish/collect_instance_version.py diff --git a/pype/plugins/nukestudio/publish/collect_leader_clip.py b/pype/plugins/hiero/publish/collect_leader_clip.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_leader_clip.py rename to pype/plugins/hiero/publish/collect_leader_clip.py index 62ef420316..bba85cf411 100644 --- a/pype/plugins/nukestudio/publish/collect_leader_clip.py +++ b/pype/plugins/hiero/publish/collect_leader_clip.py @@ -6,7 +6,7 @@ class CollectLeaderClip(api.InstancePlugin): order = api.CollectorOrder + 0.0111 label = "Collect Leader Clip" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_plates.py b/pype/plugins/hiero/publish/collect_plates.py similarity index 96% rename from pype/plugins/nukestudio/publish/collect_plates.py rename to pype/plugins/hiero/publish/collect_plates.py index 770cef7e3f..4d1cc36a92 100644 --- a/pype/plugins/nukestudio/publish/collect_plates.py +++ b/pype/plugins/hiero/publish/collect_plates.py @@ -16,7 +16,7 @@ class CollectPlates(api.InstancePlugin): # Run just before CollectSubsets order = api.CollectorOrder + 0.1021 label = "Collect Plates" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): @@ -85,7 +85,7 @@ class CollectPlatesData(api.InstancePlugin): order = api.CollectorOrder + 0.48 label = "Collect Plates Data" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["plate"] def process(self, instance): @@ -192,16 +192,17 @@ class CollectPlatesData(api.InstancePlugin): instance.data["representations"].append( plates_mov_representation) - thumb_file = head + ".png" + thumb_frame = instance.data["clipInH"] + ( + (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) + thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) - thumb_frame = instance.data["sourceIn"] + ((instance.data["sourceOut"] - instance.data["sourceIn"])/2) thumbnail = item.thumbnail(thumb_frame).save( thumb_path, format='png' ) - self.log.debug("__ sourceIn: `{}`".format(instance.data["sourceIn"])) - self.log.debug("__ thumbnail: `{}`, frame: `{}`".format(thumbnail, thumb_frame)) + self.log.debug("__ thumbnail: `{}`, frame: `{}`".format( + thumbnail, thumb_frame)) thumb_representation = { 'files': thumb_file, diff --git a/pype/plugins/nukestudio/publish/collect_remove_clip_instances.py b/pype/plugins/hiero/publish/collect_remove_clip_instances.py similarity index 94% rename from pype/plugins/nukestudio/publish/collect_remove_clip_instances.py rename to pype/plugins/hiero/publish/collect_remove_clip_instances.py index d41dc50ab1..c42bd1345b 100644 --- a/pype/plugins/nukestudio/publish/collect_remove_clip_instances.py +++ b/pype/plugins/hiero/publish/collect_remove_clip_instances.py @@ -5,7 +5,7 @@ class CollectClipSubsets(api.InstancePlugin): order = api.CollectorOrder + 0.103 label = "Collect Remove Clip Instaces" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/hiero/publish/collect_reviews.py similarity index 96% rename from pype/plugins/nukestudio/publish/collect_reviews.py rename to pype/plugins/hiero/publish/collect_reviews.py index 3167c66170..edb81aed8a 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/hiero/publish/collect_reviews.py @@ -15,7 +15,7 @@ class CollectReviews(api.InstancePlugin): # Run just before CollectSubsets order = api.CollectorOrder + 0.1022 label = "Collect Reviews" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["plate"] def process(self, instance): @@ -142,12 +142,12 @@ class CollectReviews(api.InstancePlugin): staging_dir = os.path.dirname( source_path) - thumb_file = head + ".png" + thumb_frame = instance.data["clipInH"] + ( + (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) + thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) self.log.debug("__ thumb_path: {}".format(thumb_path)) - thumb_frame = instance.data["sourceIn"] + ( - (instance.data["sourceOut"] - instance.data["sourceIn"]) / 2) self.log.debug("__ thumb_frame: {}".format(thumb_frame)) thumbnail = item.thumbnail(thumb_frame).save( thumb_path, diff --git a/pype/plugins/nukestudio/publish/collect_selection.py b/pype/plugins/hiero/publish/collect_selection.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_selection.py rename to pype/plugins/hiero/publish/collect_selection.py diff --git a/pype/plugins/nukestudio/publish/collect_sequence.py b/pype/plugins/hiero/publish/collect_sequence.py similarity index 91% rename from pype/plugins/nukestudio/publish/collect_sequence.py rename to pype/plugins/hiero/publish/collect_sequence.py index 162d1ebdfe..4247c7c4cb 100644 --- a/pype/plugins/nukestudio/publish/collect_sequence.py +++ b/pype/plugins/hiero/publish/collect_sequence.py @@ -7,7 +7,7 @@ class CollectSequence(api.ContextPlugin): order = api.CollectorOrder - 0.01 label = "Collect Sequence" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, context): context.data['activeSequence'] = hiero.ui.activeSequence() diff --git a/pype/plugins/nukestudio/publish/collect_shots.py b/pype/plugins/hiero/publish/collect_shots.py similarity index 90% rename from pype/plugins/nukestudio/publish/collect_shots.py rename to pype/plugins/hiero/publish/collect_shots.py index 7055167143..6f83e08fbe 100644 --- a/pype/plugins/nukestudio/publish/collect_shots.py +++ b/pype/plugins/hiero/publish/collect_shots.py @@ -7,7 +7,7 @@ class CollectShots(api.InstancePlugin): # Run just before CollectClipSubsets order = api.CollectorOrder + 0.1021 label = "Collect Shots" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): @@ -40,10 +40,10 @@ class CollectShots(api.InstancePlugin): data["name"] = data["subset"] + "_" + data["asset"] data["label"] = ( - "{} - {} - tasks: {} - assetbuilds: {} - comments: {}".format( + "{} - {} - tasks:{} - assetbuilds:{} - comments:{}".format( data["asset"], data["subset"], - data["tasks"], + [task.keys()[0] for task in data["tasks"]], [x["name"] for x in data.get("assetbuilds", [])], len(data.get("comments", [])) ) diff --git a/pype/plugins/nukestudio/publish/collect_tag_comments.py b/pype/plugins/hiero/publish/collect_tag_comments.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_tag_comments.py rename to pype/plugins/hiero/publish/collect_tag_comments.py index e14e53d439..76d7b6a67c 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_comments.py +++ b/pype/plugins/hiero/publish/collect_tag_comments.py @@ -6,7 +6,7 @@ class CollectClipTagComments(api.InstancePlugin): order = api.CollectorOrder + 0.013 label = "Collect Comments" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_framestart.py b/pype/plugins/hiero/publish/collect_tag_framestart.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_tag_framestart.py rename to pype/plugins/hiero/publish/collect_tag_framestart.py index 993aa99a3e..0d14271aa5 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_framestart.py +++ b/pype/plugins/hiero/publish/collect_tag_framestart.py @@ -6,7 +6,7 @@ class CollectClipTagFrameStart(api.InstancePlugin): order = api.CollectorOrder + 0.013 label = "Collect Frame Start" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_handles.py b/pype/plugins/hiero/publish/collect_tag_handles.py similarity index 98% rename from pype/plugins/nukestudio/publish/collect_tag_handles.py rename to pype/plugins/hiero/publish/collect_tag_handles.py index a6a63faea9..7e51efd8e1 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_handles.py +++ b/pype/plugins/hiero/publish/collect_tag_handles.py @@ -7,7 +7,7 @@ class CollectClipTagHandles(api.ContextPlugin): order = api.CollectorOrder + 0.012 label = "Collect Tag Handles" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, context): diff --git a/pype/plugins/nukestudio/publish/collect_tag_resolution.py b/pype/plugins/hiero/publish/collect_tag_resolution.py similarity index 95% rename from pype/plugins/nukestudio/publish/collect_tag_resolution.py rename to pype/plugins/hiero/publish/collect_tag_resolution.py index 24c13d2b4a..ef46d9d594 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_resolution.py +++ b/pype/plugins/hiero/publish/collect_tag_resolution.py @@ -6,7 +6,7 @@ class CollectClipTagResolution(api.InstancePlugin): order = api.CollectorOrder + 0.013 label = "Collect Source Resolution" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_retime.py b/pype/plugins/hiero/publish/collect_tag_retime.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_tag_retime.py rename to pype/plugins/hiero/publish/collect_tag_retime.py index 32e49e1b2a..0634130976 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_retime.py +++ b/pype/plugins/hiero/publish/collect_tag_retime.py @@ -6,7 +6,7 @@ class CollectTagRetime(api.InstancePlugin): order = api.CollectorOrder + 0.014 label = "Collect Retiming Tag" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_subsets.py b/pype/plugins/hiero/publish/collect_tag_subsets.py similarity index 97% rename from pype/plugins/nukestudio/publish/collect_tag_subsets.py rename to pype/plugins/hiero/publish/collect_tag_subsets.py index 0d42000896..d3d247727b 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_subsets.py +++ b/pype/plugins/hiero/publish/collect_tag_subsets.py @@ -6,7 +6,7 @@ class CollectClipSubsetsTags(api.InstancePlugin): order = api.CollectorOrder + 0.012 label = "Collect Tags Subsets" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/collect_tag_tasks.py b/pype/plugins/hiero/publish/collect_tag_tasks.py similarity index 77% rename from pype/plugins/nukestudio/publish/collect_tag_tasks.py rename to pype/plugins/hiero/publish/collect_tag_tasks.py index ed2f3009d3..dbcf5e5260 100644 --- a/pype/plugins/nukestudio/publish/collect_tag_tasks.py +++ b/pype/plugins/hiero/publish/collect_tag_tasks.py @@ -6,7 +6,7 @@ class CollectClipTagTasks(api.InstancePlugin): order = api.CollectorOrder + 0.012 label = "Collect Tag Tasks" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): @@ -20,8 +20,9 @@ class CollectClipTagTasks(api.InstancePlugin): # gets only task family tags and collect labels if "task" in t_family: - t_task = t_metadata.get("tag.label", "") - tasks.append(t_task) + t_task_name = t_metadata.get("tag.label", "") + t_task_type = t_metadata.get("tag.type", "") + tasks.append({t_task_name: {"type": t_task_type}}) instance.data["tasks"] = tasks diff --git a/pype/plugins/nukestudio/publish/collect_tags.py b/pype/plugins/hiero/publish/collect_tags.py similarity index 96% rename from pype/plugins/nukestudio/publish/collect_tags.py rename to pype/plugins/hiero/publish/collect_tags.py index 49005f4b22..fde6f7a26b 100644 --- a/pype/plugins/nukestudio/publish/collect_tags.py +++ b/pype/plugins/hiero/publish/collect_tags.py @@ -6,7 +6,7 @@ class CollectClipTags(api.InstancePlugin): order = api.CollectorOrder + 0.011 label = "Collect Tags" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ['clip'] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/extract_audio.py b/pype/plugins/hiero/publish/extract_audio.py similarity index 98% rename from pype/plugins/nukestudio/publish/extract_audio.py rename to pype/plugins/hiero/publish/extract_audio.py index 2c4afc8412..34fdcdfe9d 100644 --- a/pype/plugins/nukestudio/publish/extract_audio.py +++ b/pype/plugins/hiero/publish/extract_audio.py @@ -7,7 +7,7 @@ class ExtractAudioFile(pype.api.Extractor): order = api.ExtractorOrder label = "Extract Subset Audio" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["clip", "audio"] match = api.Intersection diff --git a/pype/plugins/nukestudio/publish/extract_effects.py b/pype/plugins/hiero/publish/extract_effects.py similarity index 100% rename from pype/plugins/nukestudio/publish/extract_effects.py rename to pype/plugins/hiero/publish/extract_effects.py diff --git a/pype/plugins/nukestudio/publish/extract_review_cutup_video.py b/pype/plugins/hiero/publish/extract_review_cutup_video.py similarity index 98% rename from pype/plugins/nukestudio/publish/extract_review_cutup_video.py rename to pype/plugins/hiero/publish/extract_review_cutup_video.py index d1ce3675b1..868d450fd6 100644 --- a/pype/plugins/nukestudio/publish/extract_review_cutup_video.py +++ b/pype/plugins/hiero/publish/extract_review_cutup_video.py @@ -9,7 +9,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): order = api.ExtractorOrder # order = api.CollectorOrder + 0.1023 label = "Extract Review CutUp Video" - hosts = ["nukestudio"] + hosts = ["hiero"] families = ["review"] # presets @@ -227,7 +227,7 @@ class ExtractReviewCutUpVideo(pype.api.Extractor): "step": 1, "fps": fps, "name": "cut_up_preview", - "tags": ["review", "delete"] + self.tags_addition, + "tags": ["review"] + self.tags_addition, "ext": ext, "anatomy_template": "publish" } diff --git a/pype/plugins/nukestudio/publish/validate_hierarchy.py b/pype/plugins/hiero/publish/validate_hierarchy.py similarity index 96% rename from pype/plugins/nukestudio/publish/validate_hierarchy.py rename to pype/plugins/hiero/publish/validate_hierarchy.py index 8013a98efd..d43f7fd562 100644 --- a/pype/plugins/nukestudio/publish/validate_hierarchy.py +++ b/pype/plugins/hiero/publish/validate_hierarchy.py @@ -9,7 +9,7 @@ class ValidateHierarchy(api.InstancePlugin): order = api.ValidatorOrder families = ["clip", "shot"] label = "Validate Hierarchy" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): asset_name = instance.data.get("asset", None) diff --git a/pype/plugins/nukestudio/publish/validate_names.py b/pype/plugins/hiero/publish/validate_names.py similarity index 97% rename from pype/plugins/nukestudio/publish/validate_names.py rename to pype/plugins/hiero/publish/validate_names.py index 8f7436cca0..52e4bf8ecc 100644 --- a/pype/plugins/nukestudio/publish/validate_names.py +++ b/pype/plugins/hiero/publish/validate_names.py @@ -13,7 +13,7 @@ class ValidateNames(api.InstancePlugin): families = ["clip"] match = api.Exact label = "Names" - hosts = ["nukestudio"] + hosts = ["hiero"] def process(self, instance): diff --git a/pype/plugins/nukestudio/publish/version_up_workfile.py b/pype/plugins/hiero/publish/version_up_workfile.py similarity index 95% rename from pype/plugins/nukestudio/publish/version_up_workfile.py rename to pype/plugins/hiero/publish/version_up_workfile.py index 195099dd09..893d3789eb 100644 --- a/pype/plugins/nukestudio/publish/version_up_workfile.py +++ b/pype/plugins/hiero/publish/version_up_workfile.py @@ -7,7 +7,7 @@ class VersionUpWorkfile(api.ContextPlugin): order = api.IntegratorOrder + 10.1 label = "Version-up Workfile" - hosts = ["nukestudio"] + hosts = ["hiero"] optional = True active = True diff --git a/pype/plugins/maya/create/create_render.py b/pype/plugins/maya/create/create_render.py index 6826d33c58..fa0e269126 100644 --- a/pype/plugins/maya/create/create_render.py +++ b/pype/plugins/maya/create/create_render.py @@ -188,6 +188,7 @@ class CreateRender(avalon.maya.Creator): self.data["tileRendering"] = False self.data["tilesX"] = 2 self.data["tilesY"] = 2 + self.data["convertToScanline"] = False # Disable for now as this feature is not working yet # self.data["assScene"] = False diff --git a/pype/plugins/maya/load/load_look.py b/pype/plugins/maya/load/load_look.py index b9c0d81104..c5b58c9bd5 100644 --- a/pype/plugins/maya/load/load_look.py +++ b/pype/plugins/maya/load/load_look.py @@ -3,6 +3,8 @@ from avalon import api, io import json import pype.hosts.maya.lib from collections import defaultdict +from pype.widgets.message_window import ScrollMessageBox +from Qt import QtWidgets class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): @@ -44,18 +46,33 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): + """ + Called by Scene Inventory when look should be updated to current + version. + If any reference edits cannot be applied, eg. shader renamed and + material not present, reference is unloaded and cleaned. + All failed edits are highlighted to the user via message box. + Args: + container: object that has look to be updated + representation: (dict): relationship data to get proper + representation from DB and persisted + data in .json + Returns: + None + """ import os from maya import cmds - node = container["objectName"] - path = api.get_representation_path(representation) # Get reference node from container members members = cmds.sets(node, query=True, nodesOnly=True) reference_node = self._get_reference_node(members) + shader_nodes = cmds.ls(members, type='shadingEngine') + orig_nodes = set(self._get_nodes_with_shader(shader_nodes)) + file_type = { "ma": "mayaAscii", "mb": "mayaBinary", @@ -66,6 +83,104 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): assert os.path.exists(path), "%s does not exist." % path + self._load_reference(file_type, node, path, reference_node) + + # Remove any placeHolderList attribute entries from the set that + # are remaining from nodes being removed from the referenced file. + members = cmds.sets(node, query=True) + invalid = [x for x in members if ".placeHolderList" in x] + if invalid: + cmds.sets(invalid, remove=node) + + # get new applied shaders and nodes from new version + shader_nodes = cmds.ls(members, type='shadingEngine') + nodes = set(self._get_nodes_with_shader(shader_nodes)) + + json_representation = io.find_one({ + "type": "representation", + "parent": representation['parent'], + "name": "json" + }) + + # Load relationships + shader_relation = api.get_representation_path(json_representation) + with open(shader_relation, "r") as f: + relationships = json.load(f) + + # update of reference could result in failed edits - material is not + # present because of renaming etc. + failed_edits = cmds.referenceQuery(reference_node, + editStrings=True, + failedEdits=True, + successfulEdits=False) + + # highlight failed edits to user + if failed_edits: + # clean references - removes failed reference edits + cmds.file(cr=reference_node) # cleanReference + + # reapply shading groups from json representation on orig nodes + pype.hosts.maya.lib.apply_shaders(relationships, + shader_nodes, + orig_nodes) + + msg = ["During reference update some edits failed.", + "All successful edits were kept intact.\n", + "Failed and removed edits:"] + msg.extend(failed_edits) + msg = ScrollMessageBox(QtWidgets.QMessageBox.Warning, + "Some reference edit failed", + msg) + msg.exec_() + + attributes = relationships.get("attributes", []) + + # region compute lookup + nodes_by_id = defaultdict(list) + for n in nodes: + nodes_by_id[pype.hosts.maya.lib.get_id(n)].append(n) + pype.hosts.maya.lib.apply_attributes(attributes, nodes_by_id) + + # Update metadata + cmds.setAttr("{}.representation".format(node), + str(representation["_id"]), + type="string") + + def _get_nodes_with_shader(self, shader_nodes): + """ + Returns list of nodes belonging to specific shaders + Args: + shader_nodes: of Shader groups + Returns + node names + """ + import maya.cmds as cmds + # Get container members + + nodes_list = [] + for shader in shader_nodes: + connections = cmds.listConnections(cmds.listHistory(shader, f=1), + type='mesh') + if connections: + for connection in connections: + nodes_list.extend(cmds.listRelatives(connection, + shapes=True)) + return nodes_list + + def _load_reference(self, file_type, node, path, reference_node): + """ + Load reference from 'path' on 'reference_node'. Used when change + of look (version/update) is triggered. + Args: + file_type: extension of referenced file + node: + path: (string) location of referenced file + reference_node: (string) - name of node that should be applied + on + Returns: + None + """ + import maya.cmds as cmds try: content = cmds.file(path, loadReference=reference_node, @@ -86,57 +201,10 @@ class LookLoader(pype.hosts.maya.plugin.ReferenceLoader): raise self.log.warning("Ignoring file read error:\n%s", exc) - # Fix PLN-40 for older containers created with Avalon that had the # `.verticesOnlySet` set to True. if cmds.getAttr("{}.verticesOnlySet".format(node)): self.log.info("Setting %s.verticesOnlySet to False", node) cmds.setAttr("{}.verticesOnlySet".format(node), False) - # Add new nodes of the reference to the container cmds.sets(content, forceElement=node) - - # Remove any placeHolderList attribute entries from the set that - # are remaining from nodes being removed from the referenced file. - members = cmds.sets(node, query=True) - invalid = [x for x in members if ".placeHolderList" in x] - if invalid: - cmds.sets(invalid, remove=node) - - # Get container members - shader_nodes = cmds.ls(members, type='shadingEngine') - - nodes_list = [] - for shader in shader_nodes: - connections = cmds.listConnections(cmds.listHistory(shader, f=1), - type='mesh') - if connections: - for connection in connections: - nodes_list.extend(cmds.listRelatives(connection, - shapes=True)) - nodes = set(nodes_list) - - json_representation = io.find_one({ - "type": "representation", - "parent": representation['parent'], - "name": "json" - }) - - # Load relationships - shader_relation = api.get_representation_path(json_representation) - with open(shader_relation, "r") as f: - relationships = json.load(f) - - attributes = relationships.get("attributes", []) - - # region compute lookup - nodes_by_id = defaultdict(list) - for n in nodes: - nodes_by_id[pype.hosts.maya.lib.get_id(n)].append(n) - - pype.hosts.maya.lib.apply_attributes(attributes, nodes_by_id) - - # Update metadata - cmds.setAttr("{}.representation".format(node), - str(representation["_id"]), - type="string") diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 91230fcc46..2848a55152 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -59,6 +59,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.01 hosts = ["maya"] label = "Collect Render Layers" + sync_workfile_version = False def process(self, context): """Entry point to collector.""" @@ -246,9 +247,13 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "tileRendering": render_instance.data.get("tileRendering") or False, # noqa: E501 "tilesX": render_instance.data.get("tilesX") or 2, "tilesY": render_instance.data.get("tilesY") or 2, - "priority": render_instance.data.get("priority") + "priority": render_instance.data.get("priority"), + "convertToScanline": render_instance.data.get("convertToScanline") or False # noqa: E501 } + if self.sync_workfile_version: + data["version"] = context.data["version"] + # Apply each user defined attribute as data for attr in cmds.listAttr(layer, userDefined=True) or list(): try: diff --git a/pype/plugins/maya/publish/collect_yeti_cache.py b/pype/plugins/maya/publish/collect_yeti_cache.py index 4af3e1ea18..26c3f601f6 100644 --- a/pype/plugins/maya/publish/collect_yeti_cache.py +++ b/pype/plugins/maya/publish/collect_yeti_cache.py @@ -30,7 +30,6 @@ class CollectYetiCache(pyblish.api.InstancePlugin): label = "Collect Yeti Cache" families = ["yetiRig", "yeticache"] hosts = ["maya"] - tasks = ["animation", "fx"] def process(self, instance): diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index e4048592a7..0ae19cbb81 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -262,6 +262,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): use_published = True tile_assembler_plugin = "PypeTileAssembler" + asset_dependencies = False def process(self, instance): """Plugin entry point.""" @@ -347,7 +348,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): comment = context.data.get("comment", "") dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty - deadline_user = context.data.get("deadlineUser", getpass.getuser()) + deadline_user = context.data.get("user", getpass.getuser()) jobname = "%s - %s" % (filename, instance.name) # Get the variables depending on the renderer @@ -417,9 +418,10 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Adding file dependencies. dependencies = instance.context.data["fileDependencies"] dependencies.append(filepath) - for dependency in dependencies: - key = "AssetDependency" + str(dependencies.index(dependency)) - payload_skeleton["JobInfo"][key] = dependency + if self.asset_dependencies: + for dependency in dependencies: + key = "AssetDependency" + str(dependencies.index(dependency)) + payload_skeleton["JobInfo"][key] = dependency # Handle environments ----------------------------------------------- # We need those to pass them to pype for it to set correct context @@ -731,10 +733,14 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): def _get_maya_payload(self, data): payload = copy.deepcopy(payload_skeleton) - job_info_ext = { - # Asset dependency to wait for at least the scene file to sync. - "AssetDependency0": data["filepath"], - } + if not self.asset_dependencies: + job_info_ext = {} + + else: + job_info_ext = { + # Asset dependency to wait for at least the scene file to sync. + "AssetDependency0": data["filepath"], + } plugin_info = { "SceneFile": data["filepath"], diff --git a/pype/plugins/nuke/create/create_camera b/pype/plugins/nuke/create/create_camera deleted file mode 100644 index 0d542b8ad7..0000000000 --- a/pype/plugins/nuke/create/create_camera +++ /dev/null @@ -1,3 +0,0 @@ -# create vanilla camera if no camera is selected -# if camera is selected then it will convert it into containerized object -# it is major versioned in publish diff --git a/pype/plugins/nuke/create/create_camera.py b/pype/plugins/nuke/create/create_camera.py new file mode 100644 index 0000000000..4c668925ad --- /dev/null +++ b/pype/plugins/nuke/create/create_camera.py @@ -0,0 +1,53 @@ +import avalon.nuke +from avalon.nuke import lib as anlib +import nuke + + +class CreateCamera(avalon.nuke.Creator): + """Add Publishable Backdrop""" + + name = "camera" + label = "Create 3d Camera" + family = "camera" + icon = "camera" + defaults = ["Main"] + + def __init__(self, *args, **kwargs): + super(CreateCamera, self).__init__(*args, **kwargs) + self.nodes = nuke.selectedNodes() + self.node_color = "0xff9100ff" + return + + def process(self): + nodes = list() + if (self.options or {}).get("useSelection"): + nodes = self.nodes + + if len(nodes) >= 1: + # loop selected nodes + for n in nodes: + data = self.data.copy() + if len(nodes) > 1: + # rename subset name only if more + # then one node are selected + subset = self.family + n["name"].value().capitalize() + data["subset"] = subset + + # change node color + n["tile_color"].setValue(int(self.node_color, 16)) + # add avalon knobs + anlib.imprint(n, data) + return True + else: + msg = str("Please select nodes you " + "wish to add to a container") + self.log.error(msg) + nuke.message(msg) + return + else: + # if selected is off then create one node + camera_node = nuke.createNode("Camera2") + camera_node["tile_color"].setValue(int(self.node_color, 16)) + # add avalon knobs + instance = anlib.imprint(camera_node, self.data) + return instance diff --git a/pype/plugins/nuke/load/load_backdrop.py b/pype/plugins/nuke/load/load_backdrop.py index 66f9a8e1c1..7d18893965 100644 --- a/pype/plugins/nuke/load/load_backdrop.py +++ b/pype/plugins/nuke/load/load_backdrop.py @@ -240,7 +240,6 @@ class LoadBackdropNodes(api.Loader): return update_container(GN, data_imprint) - def switch(self, container, representation): self.update(container, representation) diff --git a/pype/plugins/nuke/load/load_camera_abc.py b/pype/plugins/nuke/load/load_camera_abc.py new file mode 100644 index 0000000000..377d60e84b --- /dev/null +++ b/pype/plugins/nuke/load/load_camera_abc.py @@ -0,0 +1,187 @@ +from avalon import api, io +from avalon.nuke import lib as anlib +from avalon.nuke import containerise, update_container +import nuke + + +class AlembicCameraLoader(api.Loader): + """ + This will load alembic camera into script. + """ + + families = ["camera"] + representations = ["abc"] + + label = "Load Alembic Camera" + icon = "camera" + color = "orange" + node_color = "0x3469ffff" + + def load(self, context, name, namespace, data): + # get main variables + version = context['version'] + version_data = version.get("data", {}) + vname = version.get("name", None) + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) + fps = version_data.get("fps") or nuke.root()["fps"].getValue() + namespace = namespace or context['asset']['name'] + object_name = "{}_{}".format(name, namespace) + + # prepare data for imprinting + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["source", "author", "fps"] + + data_imprint = {"frameStart": first, + "frameEnd": last, + "version": vname, + "objectName": object_name} + + for k in add_keys: + data_imprint.update({k: version_data[k]}) + + # getting file path + file = self.fname.replace("\\", "/") + + with anlib.maintained_selection(): + camera_node = nuke.createNode( + "Camera2", + "name {} file {} read_from_file True".format( + object_name, file), + inpanel=False + ) + camera_node.forceValidate() + camera_node["frame_rate"].setValue(float(fps)) + + # workaround because nuke's bug is not adding + # animation keys properly + xpos = camera_node.xpos() + ypos = camera_node.ypos() + nuke.nodeCopy("%clipboard%") + nuke.delete(camera_node) + nuke.nodePaste("%clipboard%") + camera_node = nuke.toNode(object_name) + camera_node.setXYpos(xpos, ypos) + + # color node by correct color by actual version + self.node_version_color(version, camera_node) + + return containerise( + node=camera_node, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def update(self, container, representation): + """ + Called by Scene Inventory when look should be updated to current + version. + If any reference edits cannot be applied, eg. shader renamed and + material not present, reference is unloaded and cleaned. + All failed edits are highlighted to the user via message box. + + Args: + container: object that has look to be updated + representation: (dict): relationship data to get proper + representation from DB and persisted + data in .json + Returns: + None + """ + # Get version from io + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + object_name = container['objectName'] + # get corresponding node + camera_node = nuke.toNode(object_name) + + # get main variables + version_data = version.get("data", {}) + vname = version.get("name", None) + first = version_data.get("frameStart", None) + last = version_data.get("frameEnd", None) + fps = version_data.get("fps") or nuke.root()["fps"].getValue() + + # prepare data for imprinting + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["source", "author", "fps"] + + data_imprint = {"representation": str(representation["_id"]), + "frameStart": first, + "frameEnd": last, + "version": vname, + "objectName": object_name} + + for k in add_keys: + data_imprint.update({k: version_data[k]}) + + # getting file path + file = api.get_representation_path(representation).replace("\\", "/") + + with anlib.maintained_selection(): + camera_node = nuke.toNode(object_name) + camera_node['selected'].setValue(True) + + # collect input output dependencies + dependencies = camera_node.dependencies() + dependent = camera_node.dependent() + + camera_node["frame_rate"].setValue(float(fps)) + camera_node["file"].setValue(file) + + # workaround because nuke's bug is + # not adding animation keys properly + xpos = camera_node.xpos() + ypos = camera_node.ypos() + nuke.nodeCopy("%clipboard%") + nuke.delete(camera_node) + nuke.nodePaste("%clipboard%") + camera_node = nuke.toNode(object_name) + camera_node.setXYpos(xpos, ypos) + + # link to original input nodes + for i, input in enumerate(dependencies): + camera_node.setInput(i, input) + # link to original output nodes + for d in dependent: + index = next((i for i, dpcy in enumerate( + d.dependencies()) + if camera_node is dpcy), 0) + d.setInput(index, camera_node) + + # color node by correct color by actual version + self.node_version_color(version, camera_node) + + self.log.info("udated to version: {}".format(version.get("name"))) + + return update_container(camera_node, data_imprint) + + def node_version_color(self, version, node): + """ Coloring a node by correct color by actual version + """ + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + # change color of node + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd88467ff", 16)) + else: + node["tile_color"].setValue(int(self.node_color, 16)) + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + from avalon.nuke import viewer_update_and_undo_stop + node = nuke.toNode(container['objectName']) + with viewer_update_and_undo_stop(): + nuke.delete(node) diff --git a/pype/plugins/nuke/publish/extract_camera.py b/pype/plugins/nuke/publish/extract_camera.py new file mode 100644 index 0000000000..9a1efba1df --- /dev/null +++ b/pype/plugins/nuke/publish/extract_camera.py @@ -0,0 +1,185 @@ +import nuke +import os +import math +import pyblish.api +import pype.api +from avalon.nuke import lib as anlib +from pprint import pformat + + +class ExtractCamera(pype.api.Extractor): + """ 3D camera exctractor + """ + label = 'Exctract Camera' + order = pyblish.api.ExtractorOrder + families = ["camera"] + hosts = ["nuke"] + + # presets + write_geo_knobs = [ + ("file_type", "abc"), + ("storageFormat", "Ogawa"), + ("writeGeometries", False), + ("writePointClouds", False), + ("writeAxes", False) + ] + + def process(self, instance): + handle_start = instance.context.data["handleStart"] + handle_end = instance.context.data["handleEnd"] + first_frame = int(nuke.root()["first_frame"].getValue()) + last_frame = int(nuke.root()["last_frame"].getValue()) + step = 1 + output_range = str(nuke.FrameRange(first_frame, last_frame, step)) + + self.log.info("instance.data: `{}`".format( + pformat(instance.data))) + + rm_nodes = list() + self.log.info("Crating additional nodes") + subset = instance.data["subset"] + staging_dir = self.staging_dir(instance) + + # get extension form preset + extension = next((k[1] for k in self.write_geo_knobs + if k[0] == "file_type"), None) + if not extension: + raise RuntimeError( + "Bad config for extension in presets. " + "Talk to your supervisor or pipeline admin") + + # create file name and path + filename = subset + ".{}".format(extension) + file_path = os.path.join(staging_dir, filename).replace("\\", "/") + + with anlib.maintained_selection(): + # bake camera with axeses onto word coordinate XYZ + rm_n = bakeCameraWithAxeses( + nuke.toNode(instance.data["name"]), output_range) + rm_nodes.append(rm_n) + + # create scene node + rm_n = nuke.createNode("Scene") + rm_nodes.append(rm_n) + + # create write geo node + wg_n = nuke.createNode("WriteGeo") + wg_n["file"].setValue(file_path) + # add path to write to + for k, v in self.write_geo_knobs: + wg_n[k].setValue(v) + rm_nodes.append(wg_n) + + # write out camera + nuke.execute( + wg_n, + int(first_frame), + int(last_frame) + ) + # erase additional nodes + for n in rm_nodes: + nuke.delete(n) + + self.log.info(file_path) + + # create representation data + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': extension, + 'ext': extension, + 'files': filename, + "stagingDir": staging_dir, + "frameStart": first_frame, + "frameEnd": last_frame + } + instance.data["representations"].append(representation) + + instance.data.update({ + "path": file_path, + "outputDir": staging_dir, + "ext": extension, + "handleStart": handle_start, + "handleEnd": handle_end, + "frameStart": first_frame + handle_start, + "frameEnd": last_frame - handle_end, + "frameStartHandle": first_frame, + "frameEndHandle": last_frame, + }) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, file_path)) + + +def bakeCameraWithAxeses(camera_node, output_range): + """ Baking all perent hiearchy of axeses into camera + with transposition onto word XYZ coordinance + """ + bakeFocal = False + bakeHaperture = False + bakeVaperture = False + + camera_matrix = camera_node['world_matrix'] + + new_cam_n = nuke.createNode("Camera2") + new_cam_n.setInput(0, None) + new_cam_n['rotate'].setAnimated() + new_cam_n['translate'].setAnimated() + + old_focal = camera_node['focal'] + if old_focal.isAnimated() and not (old_focal.animation(0).constant()): + new_cam_n['focal'].setAnimated() + bakeFocal = True + else: + new_cam_n['focal'].setValue(old_focal.value()) + + old_haperture = camera_node['haperture'] + if old_haperture.isAnimated() and not ( + old_haperture.animation(0).constant()): + new_cam_n['haperture'].setAnimated() + bakeHaperture = True + else: + new_cam_n['haperture'].setValue(old_haperture.value()) + + old_vaperture = camera_node['vaperture'] + if old_vaperture.isAnimated() and not ( + old_vaperture.animation(0).constant()): + new_cam_n['vaperture'].setAnimated() + bakeVaperture = True + else: + new_cam_n['vaperture'].setValue(old_vaperture.value()) + + new_cam_n['win_translate'].setValue(camera_node['win_translate'].value()) + new_cam_n['win_scale'].setValue(camera_node['win_scale'].value()) + + for x in nuke.FrameRange(output_range): + math_matrix = nuke.math.Matrix4() + for y in range(camera_matrix.height()): + for z in range(camera_matrix.width()): + matrix_pointer = z + (y * camera_matrix.width()) + math_matrix[matrix_pointer] = camera_matrix.getValueAt( + x, (y + (z * camera_matrix.width()))) + + rot_matrix = nuke.math.Matrix4(math_matrix) + rot_matrix.rotationOnly() + rot = rot_matrix.rotationsZXY() + + new_cam_n['rotate'].setValueAt(math.degrees(rot[0]), x, 0) + new_cam_n['rotate'].setValueAt(math.degrees(rot[1]), x, 1) + new_cam_n['rotate'].setValueAt(math.degrees(rot[2]), x, 2) + new_cam_n['translate'].setValueAt( + camera_matrix.getValueAt(x, 3), x, 0) + new_cam_n['translate'].setValueAt( + camera_matrix.getValueAt(x, 7), x, 1) + new_cam_n['translate'].setValueAt( + camera_matrix.getValueAt(x, 11), x, 2) + + if bakeFocal: + new_cam_n['focal'].setValueAt(old_focal.getValueAt(x), x) + if bakeHaperture: + new_cam_n['haperture'].setValueAt(old_haperture.getValueAt(x), x) + if bakeVaperture: + new_cam_n['vaperture'].setValueAt(old_vaperture.getValueAt(x), x) + + return new_cam_n diff --git a/pype/resources/app_icons/fusion.png b/pype/resources/app_icons/fusion.png new file mode 100644 index 0000000000..50541b0e4f Binary files /dev/null and b/pype/resources/app_icons/fusion.png differ diff --git a/pype/tools/pyblish_pype/app.css b/pype/tools/pyblish_pype/app.css index 3a2c05c1f3..c51126e89f 100644 --- a/pype/tools/pyblish_pype/app.css +++ b/pype/tools/pyblish_pype/app.css @@ -459,7 +459,7 @@ QToolButton { color: #fff; } -#TerminalFilerBtn { +#TerminalFilterWidget QPushButton { /* font: %(font_size_pt)spt; */ font-family: "FontAwesome"; text-align: center; @@ -468,29 +468,58 @@ QToolButton { border-color: #777777; border-style: none; padding: 0px; - border-radius: 3px; + border-radius: 8px; +} +#TerminalFilterWidget QPushButton:hover { + background: #5f5f5f; + border-style: none; +} +#TerminalFilterWidget QPushButton:pressed { + background: #606060; + border-style: none; +} +#TerminalFilterWidget QPushButton:pressed:hover { + background: #626262; + border-style: none; } #TerminalFilerBtn[type="info"]:checked {color: rgb(255, 255, 255);} +#TerminalFilerBtn[type="info"]:hover:pressed {color: rgba(255, 255, 255, 163);} #TerminalFilerBtn[type="info"] {color: rgba(255, 255, 255, 63);} #TerminalFilerBtn[type="error"]:checked {color: rgb(255, 74, 74);} +#TerminalFilerBtn[type="error"]:hover:pressed {color: rgba(255, 74, 74, 163);} #TerminalFilerBtn[type="error"] {color: rgba(255, 74, 74, 63);} #TerminalFilerBtn[type="log_debug"]:checked {color: rgb(255, 102, 232);} #TerminalFilerBtn[type="log_debug"] {color: rgba(255, 102, 232, 63);} +#TerminalFilerBtn[type="log_debug"]:hover:pressed { + color: rgba(255, 102, 232, 163); +} #TerminalFilerBtn[type="log_info"]:checked {color: rgb(102, 171, 255);} #TerminalFilerBtn[type="log_info"] {color: rgba(102, 171, 255, 63);} +#TerminalFilerBtn[type="log_info"]:hover:pressed { + color: rgba(102, 171, 255, 163); +} #TerminalFilerBtn[type="log_warning"]:checked {color: rgb(255, 186, 102);} #TerminalFilerBtn[type="log_warning"] {color: rgba(255, 186, 102, 63);} +#TerminalFilerBtn[type="log_warning"]:hover:pressed { + color: rgba(255, 186, 102, 163); +} #TerminalFilerBtn[type="log_error"]:checked {color: rgb(255, 77, 88);} #TerminalFilerBtn[type="log_error"] {color: rgba(255, 77, 88, 63);} +#TerminalFilerBtn[type="log_error"]:hover:pressed { + color: rgba(255, 77, 88, 163); +} #TerminalFilerBtn[type="log_critical"]:checked {color: rgb(255, 79, 117);} #TerminalFilerBtn[type="log_critical"] {color: rgba(255, 79, 117, 63);} +#TerminalFilerBtn[type="log_critical"]:hover:pressed { + color: rgba(255, 79, 117, 163); +} #SuspendLogsBtn { background: #444; diff --git a/pype/tools/pyblish_pype/app.py b/pype/tools/pyblish_pype/app.py index 0f662d5b3e..9879c63030 100644 --- a/pype/tools/pyblish_pype/app.py +++ b/pype/tools/pyblish_pype/app.py @@ -92,7 +92,6 @@ def show(parent=None): self._window.show() self._window.activateWindow() - self._window.resize(*settings.WindowSize) self._window.setWindowTitle(settings.WindowTitle) font = QtGui.QFont("Open Sans", 8, QtGui.QFont.Normal) @@ -100,5 +99,6 @@ def show(parent=None): self._window.setStyleSheet(css) self._window.reset() + self._window.resize(*settings.WindowSize) return self._window diff --git a/pype/tools/pyblish_pype/view.py b/pype/tools/pyblish_pype/view.py index 477303eae8..248c1fbbf9 100644 --- a/pype/tools/pyblish_pype/view.py +++ b/pype/tools/pyblish_pype/view.py @@ -83,6 +83,7 @@ class OverviewView(QtWidgets.QTreeView): self.setHeaderHidden(True) self.setRootIsDecorated(False) self.setIndentation(0) + self.setAnimated(True) def event(self, event): if not event.type() == QtCore.QEvent.KeyPress: @@ -159,6 +160,8 @@ class InstanceView(OverviewView): def __init__(self, parent=None): super(InstanceView, self).__init__(parent) self.viewport().setMouseTracking(True) + self._pressed_group_index = None + self._pressed_expander = None def mouseMoveEvent(self, event): index = self.indexAt(event.pos()) @@ -176,6 +179,8 @@ class InstanceView(OverviewView): self.collapse(index) def group_toggle(self, index): + if not index.isValid(): + return model = index.model() chilren_indexes_checked = [] @@ -201,25 +206,90 @@ class InstanceView(OverviewView): model.setData(index, new_state, QtCore.Qt.CheckStateRole) self.toggled.emit(index, new_state) - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton: + def _mouse_press(self, event): + if event.button() != QtCore.Qt.LeftButton: + return + + self._pressed_group_index = None + self._pressed_expander = None + + pos_index = self.indexAt(event.pos()) + if not pos_index.isValid(): + return + + if pos_index.data(Roles.TypeRole) != model.InstanceType: + self._pressed_group_index = pos_index + if event.pos().x() < 20: + self._pressed_expander = True + else: + self._pressed_expander = False + + elif event.pos().x() < 20: indexes = self.selectionModel().selectedIndexes() - if len(indexes) == 1: - index = indexes[0] - pos_index = self.indexAt(event.pos()) - if index == pos_index: - # If instance or Plugin - if index.data(Roles.TypeRole) == model.InstanceType: - if event.pos().x() < 20: - self.toggled.emit(index, None) - elif event.pos().x() > self.width() - 20: - self.show_perspective.emit(index) - else: - if event.pos().x() < EXPANDER_WIDTH: - self.item_expand(index) - else: - self.group_toggle(index) - self.item_expand(index, True) + any_checked = False + if len(indexes) <= 1: + return + + if pos_index in indexes: + for index in indexes: + if index.data(QtCore.Qt.CheckStateRole): + any_checked = True + break + + for index in indexes: + self.toggled.emit(index, not any_checked) + return True + self.toggled.emit(pos_index, not any_checked) + + def mousePressEvent(self, event): + if self._mouse_press(event): + return + return super(InstanceView, self).mousePressEvent(event) + + def _mouse_release(self, event, pressed_expander, pressed_index): + if event.button() != QtCore.Qt.LeftButton: + return + + pos_index = self.indexAt(event.pos()) + if not pos_index.isValid(): + return + + if pos_index.data(Roles.TypeRole) == model.InstanceType: + indexes = self.selectionModel().selectedIndexes() + if len(indexes) == 1 and indexes[0] == pos_index: + if event.pos().x() < 20: + self.toggled.emit(indexes[0], None) + elif event.pos().x() > self.width() - 20: + self.show_perspective.emit(indexes[0]) + return True + return + + if pressed_index != pos_index: + return + + if self.state() == QtWidgets.QTreeView.State.DragSelectingState: + indexes = self.selectionModel().selectedIndexes() + if len(indexes) != 1 or indexes[0] != pos_index: + return + + if event.pos().x() < EXPANDER_WIDTH: + if pressed_expander is True: + self.item_expand(pos_index) + return True + else: + if pressed_expander is False: + self.group_toggle(pos_index) + self.item_expand(pos_index, True) + return True + + def mouseReleaseEvent(self, event): + pressed_index = self._pressed_group_index + pressed_expander = self._pressed_expander is True + self._pressed_group_index = None + self._pressed_expander = None + result = self._mouse_release(event, pressed_expander, pressed_index) + if result: + return return super(InstanceView, self).mouseReleaseEvent(event) diff --git a/pype/tools/pyblish_pype/widgets.py b/pype/tools/pyblish_pype/widgets.py index 880d4755ad..4da759899e 100644 --- a/pype/tools/pyblish_pype/widgets.py +++ b/pype/tools/pyblish_pype/widgets.py @@ -6,7 +6,7 @@ from .constants import PluginStates, InstanceStates, Roles class EllidableLabel(QtWidgets.QLabel): def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(EllidableLabel, self).__init__(*args, **kwargs) self.setObjectName("EllidableLabel") def paintEvent(self, event): @@ -21,7 +21,7 @@ class EllidableLabel(QtWidgets.QLabel): class PerspectiveLabel(QtWidgets.QTextEdit): def __init__(self, parent=None): - super(self.__class__, self).__init__(parent) + super(PerspectiveLabel, self).__init__(parent) self.setObjectName("PerspectiveLabel") size_policy = self.sizePolicy() @@ -50,7 +50,7 @@ class PerspectiveLabel(QtWidgets.QTextEdit): return margins.top() + document.size().height() + margins.bottom() def sizeHint(self): - width = super(self.__class__, self).sizeHint().width() + width = super(PerspectiveLabel, self).sizeHint().width() return QtCore.QSize(width, self.heightForWidth(width)) @@ -328,7 +328,7 @@ class PerspectiveWidget(QtWidgets.QWidget): self.records.toggle_content(len_records > 0) def toggle_me(self): - self.parent_widget.toggle_perspective_widget() + self.parent_widget.parent().toggle_perspective_widget() class ClickableWidget(QtWidgets.QLabel): @@ -407,7 +407,7 @@ class ExpandableWidget(QtWidgets.QWidget): self.content_widget.setVisible(checked) def resizeEvent(self, event): - super(self.__class__, self).resizeEvent(event) + super(ExpandableWidget, self).resizeEvent(event) self.content.updateGeometry() def set_content(self, in_widget): @@ -481,7 +481,7 @@ class CommentBox(QtWidgets.QLineEdit): class TerminalDetail(QtWidgets.QTextEdit): def __init__(self, text, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) + super(TerminalDetail, self).__init__(*args, **kwargs) self.setReadOnly(True) self.setHtml(text) @@ -504,7 +504,7 @@ class FilterButton(QtWidgets.QPushButton): def __init__(self, name, *args, **kwargs): self.filter_name = name - super(self.__class__, self).__init__(*args, **kwargs) + super(FilterButton, self).__init__(*args, **kwargs) self.toggled.connect(self.on_toggle) @@ -522,8 +522,8 @@ class FilterButton(QtWidgets.QPushButton): class TerminalFilterWidget(QtWidgets.QWidget): # timer.timeout.connect(lambda: self._update(self.parent_widget)) def __init__(self, *args, **kwargs): - super(self.__class__, self).__init__(*args, **kwargs) - + super(TerminalFilterWidget, self).__init__(*args, **kwargs) + self.setObjectName("TerminalFilterWidget") self.filter_changed = QtCore.Signal() info_icon = awesome.tags["info"] @@ -531,16 +531,16 @@ class TerminalFilterWidget(QtWidgets.QWidget): error_icon = awesome.tags["exclamation-triangle"] filter_buttons = ( - FilterButton("info", info_icon), - FilterButton("log_debug", log_icon), - FilterButton("log_info", log_icon), - FilterButton("log_warning", log_icon), - FilterButton("log_error", log_icon), - FilterButton("log_critical", log_icon), - FilterButton("error", error_icon) + FilterButton("info", info_icon, self), + FilterButton("log_debug", log_icon, self), + FilterButton("log_info", log_icon, self), + FilterButton("log_warning", log_icon, self), + FilterButton("log_error", log_icon, self), + FilterButton("log_critical", log_icon, self), + FilterButton("error", error_icon, self) ) - layout = QtWidgets.QHBoxLayout() + layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) # Add spacers layout.addWidget(QtWidgets.QWidget(), 1) diff --git a/pype/tools/pyblish_pype/window.py b/pype/tools/pyblish_pype/window.py index 2a037ba4bc..ab59f9f6a1 100644 --- a/pype/tools/pyblish_pype/window.py +++ b/pype/tools/pyblish_pype/window.py @@ -39,6 +39,7 @@ Todo: the first time to understand how to actually to it! """ +import sys from functools import partial from . import delegate, model, settings, util, view, widgets @@ -48,6 +49,10 @@ from Qt import QtCore, QtGui, QtWidgets from .constants import ( PluginStates, PluginActionStates, InstanceStates, GroupStates, Roles ) +if sys.version_info[0] == 3: + from queue import Queue +else: + from Queue import Queue class Window(QtWidgets.QDialog): @@ -225,7 +230,6 @@ class Window(QtWidgets.QDialog): intent_model = model.IntentModel() intent_box.setModel(intent_model) - intent_box.currentIndexChanged.connect(self.on_intent_changed) comment_intent_widget = QtWidgets.QWidget() comment_intent_layout = QtWidgets.QHBoxLayout(comment_intent_widget) @@ -267,6 +271,7 @@ class Window(QtWidgets.QDialog): layout.addWidget(footer_button_play, 0) footer_layout = QtWidgets.QVBoxLayout(footer_widget) + footer_layout.addWidget(terminal_filters_widget) footer_layout.addWidget(comment_intent_widget) footer_layout.addLayout(layout) @@ -281,16 +286,21 @@ class Window(QtWidgets.QDialog): ) closing_placeholder.hide() - perspective_widget = widgets.PerspectiveWidget(self) + perspective_widget = widgets.PerspectiveWidget(main_widget) perspective_widget.hide() + pages_widget = QtWidgets.QWidget(main_widget) + layout = QtWidgets.QVBoxLayout(pages_widget) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(header_widget, 0) + layout.addWidget(body_widget, 1) + # Main layout layout = QtWidgets.QVBoxLayout(main_widget) - layout.addWidget(header_widget, 0) - layout.addWidget(body_widget, 3) + layout.addWidget(pages_widget, 3) layout.addWidget(perspective_widget, 3) layout.addWidget(closing_placeholder, 1) - layout.addWidget(terminal_filters_widget, 0) layout.addWidget(footer_widget, 0) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) @@ -382,6 +392,7 @@ class Window(QtWidgets.QDialog): # Enable CSS on plain QWidget objects for _widget in ( + pages_widget, header_widget, body_widget, artist_page, @@ -457,6 +468,7 @@ class Window(QtWidgets.QDialog): self.main_widget = main_widget + self.pages_widget = pages_widget self.header_widget = header_widget self.body_widget = body_widget @@ -498,13 +510,20 @@ class Window(QtWidgets.QDialog): "overview": header_tab_overview, "terminal": header_tab_terminal } - self.pages = { - "artist": artist_page, - "overview": overview_page, - "terminal": terminal_page - } + self.pages = ( + ("artist", artist_page), + ("overview", overview_page), + ("terminal", terminal_page) + ) current_page = settings.InitialTab or "artist" + self.comment_main_widget.setVisible( + not current_page == "terminal" + ) + self.terminal_filters_widget.setVisible( + current_page == "terminal" + ) + self.state = { "is_closing": False, "current_page": current_page @@ -548,11 +567,9 @@ class Window(QtWidgets.QDialog): show = True self.perspective_widget.set_context(index) - self.body_widget.setVisible(not show) - self.header_widget.setVisible(not show) - + self.pages_widget.setVisible(not show) self.perspective_widget.setVisible(show) - self.terminal_filters_widget.setVisible(show) + self.footer_items_visibility() def change_toggleability(self, enable_value): for plugin_item in self.plugin_model.plugin_items.values(): @@ -563,6 +580,20 @@ class Window(QtWidgets.QDialog): ): instance_item.setData(enable_value, Roles.IsEnabledRole) + def _add_intent_to_context(self): + if ( + self.intent_model.has_items + and "intent" not in self.controller.context.data + ): + idx = self.intent_model.index(self.intent_box.currentIndex(), 0) + intent_value = self.intent_model.data(idx, Roles.IntentItemValue) + intent_label = self.intent_model.data(idx, QtCore.Qt.DisplayRole) + + self.controller.context.data["intent"] = { + "value": intent_value, + "label": intent_label + } + def on_instance_toggle(self, index, state=None): """An item is requesting to be toggled""" if not index.data(Roles.IsOptionalRole): @@ -603,27 +634,245 @@ class Window(QtWidgets.QDialog): self.update_compatibility() def on_tab_changed(self, target): - self.comment_main_widget.setVisible(not target == "terminal") - self.terminal_filters_widget.setVisible(target == "terminal") - - for name, page in self.pages.items(): - if name != target: - page.hide() - - self.pages[target].show() + previous_page = None + target_page = None + direction = None + for name, page in self.pages: + if name == target: + target_page = page + if direction is None: + direction = -1 + elif name == self.state["current_page"]: + previous_page = page + if direction is None: + direction = 1 + else: + page.setVisible(False) self.state["current_page"] = target + self.slide_page(previous_page, target_page, direction) + + def slide_page(self, previous_page, target_page, direction): + if previous_page is None: + for name, page in self.pages: + for _name, _page in self.pages: + if name != _name: + _page.hide() + page.show() + page.hide() + + if ( + previous_page == target_page + or previous_page is None + ): + if not target_page.isVisible(): + target_page.show() + return + + width = previous_page.frameGeometry().width() + offset = QtCore.QPoint(direction * width, 0) + + previous_rect = ( + previous_page.frameGeometry().x(), + previous_page.frameGeometry().y(), + width, + previous_page.frameGeometry().height() + ) + curr_pos = previous_page.pos() + + previous_page.hide() + target_page.show() + target_page.update() + target_rect = ( + target_page.frameGeometry().x(), + target_page.frameGeometry().y(), + target_page.frameGeometry().width(), + target_page.frameGeometry().height() + ) + previous_page.show() + + target_page.raise_() + previous_page.setGeometry(*previous_rect) + target_page.setGeometry(*target_rect) + + target_page.move(curr_pos + offset) + + duration = 250 + + anim_old = QtCore.QPropertyAnimation( + previous_page, b"pos", self + ) + anim_old.setDuration(duration) + anim_old.setStartValue(curr_pos) + anim_old.setEndValue(curr_pos - offset) + anim_old.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim_new = QtCore.QPropertyAnimation( + target_page, b"pos", self + ) + anim_new.setDuration(duration) + anim_new.setStartValue(curr_pos + offset) + anim_new.setEndValue(curr_pos) + anim_new.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim_group = QtCore.QParallelAnimationGroup(self) + anim_group.addAnimation(anim_old) + anim_group.addAnimation(anim_new) + + def slide_finished(): + previous_page.hide() + self.footer_items_visibility() + + anim_group.finished.connect(slide_finished) + anim_group.start() + + def footer_items_visibility( + self, + comment_visible=None, + terminal_filters_visibile=None + ): + target = self.state["current_page"] + comment_visibility = ( + not self.perspective_widget.isVisible() + and not target == "terminal" + and self.comment_box.isEnabled() + ) + terminal_filters_visibility = ( + target == "terminal" + or self.perspective_widget.isVisible() + ) + + if comment_visible is not None and comment_visibility: + comment_visibility = comment_visible + + if ( + terminal_filters_visibile is not None + and terminal_filters_visibility + ): + terminal_filters_visibility = terminal_filters_visibile + + duration = 150 + + hiding_widgets = [] + showing_widgets = [] + if (comment_visibility != ( + self.comment_main_widget.isVisible() + )): + if self.comment_main_widget.isVisible(): + hiding_widgets.append(self.comment_main_widget) + else: + showing_widgets.append(self.comment_main_widget) + + if (terminal_filters_visibility != ( + self.terminal_filters_widget.isVisible() + )): + if self.terminal_filters_widget.isVisible(): + hiding_widgets.append(self.terminal_filters_widget) + else: + showing_widgets.append(self.terminal_filters_widget) + + if not hiding_widgets and not showing_widgets: + return + + hiding_widgets_queue = Queue() + showing_widgets_queue = Queue() + widgets_by_pos_y = {} + for widget in hiding_widgets: + key = widget.mapToGlobal(widget.rect().topLeft()).x() + widgets_by_pos_y[key] = widget + + for key in sorted(widgets_by_pos_y.keys()): + widget = widgets_by_pos_y[key] + hiding_widgets_queue.put((widget, )) + + for widget in hiding_widgets: + widget.hide() + + for widget in showing_widgets: + widget.show() + + self.footer_widget.updateGeometry() + widgets_by_pos_y = {} + for widget in showing_widgets: + key = widget.mapToGlobal(widget.rect().topLeft()).x() + widgets_by_pos_y[key] = widget + + for key in reversed(sorted(widgets_by_pos_y.keys())): + widget = widgets_by_pos_y[key] + showing_widgets_queue.put(widget) + + for widget in showing_widgets: + widget.hide() + + for widget in hiding_widgets: + widget.show() + + def process_showing(): + if showing_widgets_queue.empty(): + return + + widget = showing_widgets_queue.get() + widget.show() + + widget_rect = widget.frameGeometry() + second_rect = QtCore.QRect(widget_rect) + second_rect.setTopLeft(second_rect.bottomLeft()) + + animation = QtCore.QPropertyAnimation( + widget, b"geometry", self + ) + animation.setDuration(duration) + animation.setStartValue(second_rect) + animation.setEndValue(widget_rect) + animation.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + animation.finished.connect(process_showing) + animation.start() + + def process_hiding(): + if hiding_widgets_queue.empty(): + return process_showing() + + item = hiding_widgets_queue.get() + if isinstance(item, tuple): + widget = item[0] + hiding_widgets_queue.put(widget) + widget_rect = widget.frameGeometry() + second_rect = QtCore.QRect(widget_rect) + second_rect.setTopLeft(second_rect.bottomLeft()) + + anim = QtCore.QPropertyAnimation( + widget, b"geometry", self + ) + anim.setDuration(duration) + anim.setStartValue(widget_rect) + anim.setEndValue(second_rect) + anim.setEasingCurve(QtCore.QEasingCurve.OutQuad) + + anim.finished.connect(process_hiding) + anim.start() + else: + item.hide() + return process_hiding() + + process_hiding() def on_validate_clicked(self): self.comment_box.setEnabled(False) + self.footer_items_visibility() self.intent_box.setEnabled(False) + self._add_intent_to_context() + self.validate() def on_play_clicked(self): self.comment_box.setEnabled(False) + self.footer_items_visibility() self.intent_box.setEnabled(False) + self._add_intent_to_context() + self.publish() def on_reset_clicked(self): @@ -644,7 +893,7 @@ class Window(QtWidgets.QDialog): def apply_log_suspend_value(self, value): self._suspend_logs = value if self.state["current_page"] == "terminal": - self.on_tab_changed("overview") + self.tabs["overview"].setChecked(True) self.tabs["terminal"].setVisible(not self._suspend_logs) @@ -652,18 +901,6 @@ class Window(QtWidgets.QDialog): """The user has typed a comment.""" self.controller.context.data["comment"] = self.comment_box.text() - def on_intent_changed(self): - idx = self.intent_model.index(self.intent_box.currentIndex(), 0) - intent_value = self.intent_model.data(idx, Roles.IntentItemValue) - intent_label = self.intent_model.data(idx, QtCore.Qt.DisplayRole) - - # TODO move to play - if self.controller.context: - self.controller.context.data["intent"] = { - "value": intent_value, - "label": intent_label - } - def on_about_to_process(self, plugin, instance): """Reflect currently running pair in GUI""" if instance is None: @@ -753,9 +990,8 @@ class Window(QtWidgets.QDialog): comment = self.controller.context.data.get("comment") self.comment_box.setText(comment or None) self.comment_box.setEnabled(True) + self.footer_items_visibility() - if self.intent_model.has_items: - self.on_intent_changed() self.intent_box.setEnabled(True) # Refresh tab diff --git a/pype/tools/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py index aba8e6c0a4..85a574f8dc 100644 --- a/pype/tools/standalonepublish/__main__.py +++ b/pype/tools/standalonepublish/__main__.py @@ -1,15 +1,26 @@ import os import sys import app +import ctypes import signal -from Qt import QtWidgets +from Qt import QtWidgets, QtGui from avalon import style +from pype.api import resources if __name__ == "__main__": + + # Allow to change icon of running process in windows taskbar + if os.name == "nt": + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( + u"standalonepublish" + ) + qt_app = QtWidgets.QApplication([]) # app.setQuitOnLastWindowClosed(False) qt_app.setStyleSheet(style.load_stylesheet()) + icon = QtGui.QIcon(resources.pype_icon_filepath()) + qt_app.setWindowIcon(icon) def signal_handler(sig, frame): print("You pressed Ctrl+C. Process ended.") diff --git a/pype/widgets/message_window.py b/pype/widgets/message_window.py index 41c709b933..969d6ccdd1 100644 --- a/pype/widgets/message_window.py +++ b/pype/widgets/message_window.py @@ -1,4 +1,4 @@ -from Qt import QtWidgets +from Qt import QtWidgets, QtCore import sys import logging @@ -49,6 +49,17 @@ class Window(QtWidgets.QWidget): def message(title=None, message=None, level="info", parent=None): + """ + Produces centered dialog with specific level denoting severity + Args: + title: (string) dialog title + message: (string) message + level: (string) info|warning|critical + parent: (QtWidgets.QApplication) + + Returns: + None + """ app = parent if not app: app = QtWidgets.QApplication(sys.argv) @@ -68,3 +79,60 @@ def message(title=None, message=None, level="info", parent=None): # skip all possible issues that may happen feature is not crutial log.warning("Couldn't center message.", exc_info=True) # sys.exit(app.exec_()) + + +class ScrollMessageBox(QtWidgets.QDialog): + """ + Basic version of scrollable QMessageBox. No other existing dialog + implementation is scrollable. + Args: + icon: + title: + messages: of messages + cancelable: - True if Cancel button should be added + """ + def __init__(self, icon, title, messages, cancelable=False): + super(ScrollMessageBox, self).__init__() + self.setWindowTitle(title) + self.icon = icon + + self.setWindowFlags(QtCore.Qt.WindowTitleHint) + + layout = QtWidgets.QVBoxLayout(self) + + scroll_widget = QtWidgets.QScrollArea(self) + scroll_widget.setWidgetResizable(True) + content_widget = QtWidgets.QWidget(self) + scroll_widget.setWidget(content_widget) + + max_len = 0 + content_layout = QtWidgets.QVBoxLayout(content_widget) + for message in messages: + label_widget = QtWidgets.QLabel(message, content_widget) + content_layout.addWidget(label_widget) + max_len = max(max_len, len(message)) + + # guess size of scrollable area + max_width = QtWidgets.QApplication.desktop().availableGeometry().width + scroll_widget.setMinimumWidth(min(max_width, max_len * 6)) + layout.addWidget(scroll_widget) + + if not cancelable: # if no specific buttons OK only + buttons = QtWidgets.QDialogButtonBox.Ok + else: + buttons = QtWidgets.QDialogButtonBox.Ok | \ + QtWidgets.QDialogButtonBox.Cancel + + btn_box = QtWidgets.QDialogButtonBox(buttons) + btn_box.accepted.connect(self.accept) + + if cancelable: + btn_box.reject.connect(self.reject) + + btn = QtWidgets.QPushButton('Copy to clipboard') + btn.clicked.connect(lambda: QtWidgets.QApplication. + clipboard().setText("\n".join(messages))) + btn_box.addButton(btn, QtWidgets.QDialogButtonBox.NoRole) + + layout.addWidget(btn_box) + self.show() diff --git a/schema/config-1.1.json b/schema/config-1.1.json new file mode 100644 index 0000000000..ea5ab0ff27 --- /dev/null +++ b/schema/config-1.1.json @@ -0,0 +1,87 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "pype:config-1.1", + "description": "A project configuration.", + + "type": "object", + + "additionalProperties": false, + "required": [ + "tasks", + "apps" + ], + + "properties": { + "schema": { + "description": "Schema identifier for payload", + "type": "string" + }, + "template": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^.*$": { + "type": "string" + } + } + }, + "tasks": { + "type": "object", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "group": {"type": "string"}, + "label": {"type": "string"} + }, + "required": [ + "short_name" + ] + } + }, + "apps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "group": {"type": "string"}, + "label": {"type": "string"} + }, + "required": ["name"] + } + }, + "families": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "label": {"type": "string"}, + "hideFilter": {"type": "boolean"} + }, + "required": ["name"] + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "icon": {"type": "string"}, + "color": {"type": "string"}, + "order": {"type": ["integer", "number"]} + }, + "required": ["name"] + } + }, + "copy": { + "type": "object" + } + } +} diff --git a/schema/inventory-1.1.json b/schema/inventory-1.1.json new file mode 100644 index 0000000000..1b572b7d23 --- /dev/null +++ b/schema/inventory-1.1.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "pype:config-1.1", + "description": "A project configuration.", + + "type": "object", + + "additionalProperties": true +} diff --git a/schema/project-2.1.json b/schema/project-2.1.json new file mode 100644 index 0000000000..40e3bdb638 --- /dev/null +++ b/schema/project-2.1.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + + "title": "pype:project-2.1", + "description": "A unit of data", + + "type": "object", + + "additionalProperties": true, + + "required": [ + "schema", + "type", + "name", + "data", + "config" + ], + + "properties": { + "schema": { + "description": "Schema identifier for payload", + "type": "string", + "enum": ["avalon-core:project-2.1", "pype:project-2.1"], + "example": "avalon-core:project-2.1" + }, + "type": { + "description": "The type of document", + "type": "string", + "enum": ["project"], + "example": "project" + }, + "parent": { + "description": "Unique identifier to parent document", + "example": "592c33475f8c1b064c4d1696" + }, + "name": { + "description": "Name of directory", + "type": "string", + "pattern": "^[a-zA-Z0-9_.]*$", + "example": "hulk" + }, + "data": { + "description": "Document metadata", + "type": "object", + "example": { + "fps": 24, + "width": 1920, + "height": 1080 + } + }, + "config": { + "type": "object", + "description": "Document metadata", + "example": { + "schema": "pype:config-1.1", + "apps": [ + { + "name": "maya2016", + "label": "Autodesk Maya 2016" + }, + { + "name": "nuke10", + "label": "The Foundry Nuke 10.0" + } + ], + "tasks": { + "Model": {"short_name": "mdl"}, + "Render": {"short_name": "rnd"}, + "Animate": {"short_name": "anim"}, + "Rig": {"short_name": "rig"}, + "Lookdev": {"short_name": "look"}, + "Layout": {"short_name": "lay"} + }, + "template": { + "work": + "{root}/{project}/{silo}/{asset}/work/{task}/{app}", + "publish": + "{root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/{subset}.{representation}" + } + }, + "$ref": "config-1.1.json" + } + }, + + "definitions": {} +} diff --git a/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox b/setup/hiero/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox similarity index 100% rename from setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox rename to setup/hiero/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox diff --git a/setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png b/setup/hiero/hiero_plugin_path/Icons/1_add_handles_end.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/1_add_handles_end.png rename to setup/hiero/hiero_plugin_path/Icons/1_add_handles_end.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png b/setup/hiero/hiero_plugin_path/Icons/2_add_handles.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/2_add_handles.png rename to setup/hiero/hiero_plugin_path/Icons/2_add_handles.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/3D.png b/setup/hiero/hiero_plugin_path/Icons/3D.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/3D.png rename to setup/hiero/hiero_plugin_path/Icons/3D.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png b/setup/hiero/hiero_plugin_path/Icons/3_add_handles_start.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/3_add_handles_start.png rename to setup/hiero/hiero_plugin_path/Icons/3_add_handles_start.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/4_2D.png b/setup/hiero/hiero_plugin_path/Icons/4_2D.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/4_2D.png rename to setup/hiero/hiero_plugin_path/Icons/4_2D.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/edit.png b/setup/hiero/hiero_plugin_path/Icons/edit.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/edit.png rename to setup/hiero/hiero_plugin_path/Icons/edit.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/fusion.png b/setup/hiero/hiero_plugin_path/Icons/fusion.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/fusion.png rename to setup/hiero/hiero_plugin_path/Icons/fusion.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png b/setup/hiero/hiero_plugin_path/Icons/hierarchy.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/hierarchy.png rename to setup/hiero/hiero_plugin_path/Icons/hierarchy.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/houdini.png b/setup/hiero/hiero_plugin_path/Icons/houdini.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/houdini.png rename to setup/hiero/hiero_plugin_path/Icons/houdini.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/layers.psd b/setup/hiero/hiero_plugin_path/Icons/layers.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/layers.psd rename to setup/hiero/hiero_plugin_path/Icons/layers.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/lense.png b/setup/hiero/hiero_plugin_path/Icons/lense.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/lense.png rename to setup/hiero/hiero_plugin_path/Icons/lense.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/lense1.png b/setup/hiero/hiero_plugin_path/Icons/lense1.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/lense1.png rename to setup/hiero/hiero_plugin_path/Icons/lense1.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/maya.png b/setup/hiero/hiero_plugin_path/Icons/maya.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/maya.png rename to setup/hiero/hiero_plugin_path/Icons/maya.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/nuke.png b/setup/hiero/hiero_plugin_path/Icons/nuke.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/nuke.png rename to setup/hiero/hiero_plugin_path/Icons/nuke.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/resolution.png b/setup/hiero/hiero_plugin_path/Icons/resolution.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/resolution.png rename to setup/hiero/hiero_plugin_path/Icons/resolution.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/resolution.psd b/setup/hiero/hiero_plugin_path/Icons/resolution.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/resolution.psd rename to setup/hiero/hiero_plugin_path/Icons/resolution.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/retiming.png b/setup/hiero/hiero_plugin_path/Icons/retiming.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/retiming.png rename to setup/hiero/hiero_plugin_path/Icons/retiming.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/retiming.psd b/setup/hiero/hiero_plugin_path/Icons/retiming.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/retiming.psd rename to setup/hiero/hiero_plugin_path/Icons/retiming.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/review.png b/setup/hiero/hiero_plugin_path/Icons/review.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/review.png rename to setup/hiero/hiero_plugin_path/Icons/review.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/review.psd b/setup/hiero/hiero_plugin_path/Icons/review.psd similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/review.psd rename to setup/hiero/hiero_plugin_path/Icons/review.psd diff --git a/setup/nukestudio/hiero_plugin_path/Icons/volume.png b/setup/hiero/hiero_plugin_path/Icons/volume.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/volume.png rename to setup/hiero/hiero_plugin_path/Icons/volume.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png b/setup/hiero/hiero_plugin_path/Icons/z_layer_bg.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/z_layer_bg.png rename to setup/hiero/hiero_plugin_path/Icons/z_layer_bg.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png b/setup/hiero/hiero_plugin_path/Icons/z_layer_fg.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/z_layer_fg.png rename to setup/hiero/hiero_plugin_path/Icons/z_layer_fg.png diff --git a/setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png b/setup/hiero/hiero_plugin_path/Icons/z_layer_main.png similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Icons/z_layer_main.png rename to setup/hiero/hiero_plugin_path/Icons/z_layer_main.png diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py b/setup/hiero/hiero_plugin_path/Python/Startup/SpreadsheetExport.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py rename to setup/hiero/hiero_plugin_path/Python/Startup/SpreadsheetExport.py diff --git a/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py b/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py new file mode 100644 index 0000000000..cdf85c524f --- /dev/null +++ b/setup/hiero/hiero_plugin_path/Python/Startup/Startup.py @@ -0,0 +1,19 @@ +import traceback + +# activate hiero from pype +import avalon.api +import pype.hosts.hiero +avalon.api.install(pype.hosts.hiero) + +try: + __import__("pype.hosts.hiero") + __import__("pyblish") + +except ImportError as e: + print(traceback.format_exc()) + print("pyblish: Could not load integration: %s " % e) + +else: + # Setup integration + import pype.hosts.hiero.lib + pype.hosts.hiero.lib.setup() diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py b/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py rename to setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py b/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py rename to setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py b/setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/__init__.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py rename to setup/hiero/hiero_plugin_path/Python/Startup/otioexporter/__init__.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/project_helpers.py b/setup/hiero/hiero_plugin_path/Python/Startup/project_helpers.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/project_helpers.py rename to setup/hiero/hiero_plugin_path/Python/Startup/project_helpers.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py b/setup/hiero/hiero_plugin_path/Python/Startup/selection_tracker.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py rename to setup/hiero/hiero_plugin_path/Python/Startup/selection_tracker.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py b/setup/hiero/hiero_plugin_path/Python/Startup/setFrameRate.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py rename to setup/hiero/hiero_plugin_path/Python/Startup/setFrameRate.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py b/setup/hiero/hiero_plugin_path/Python/Startup/version_everywhere.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py rename to setup/hiero/hiero_plugin_path/Python/Startup/version_everywhere.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/Purge.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/Purge.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py b/setup/hiero/hiero_plugin_path/Python/StartupUI/setPosterFrame.py similarity index 100% rename from setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py rename to setup/hiero/hiero_plugin_path/Python/StartupUI/setPosterFrame.py diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 99% rename from setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml index e24a4dbe4e..690820c788 100644 --- a/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml +++ b/setup/hiero/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -1,6 +1,6 @@ 991 - //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + //10.11.0.184/171001_ftrack/tgbvfx/editorial/hiero/workspace/ 1 True 3 diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 99% rename from setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml index e24a4dbe4e..690820c788 100644 --- a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml +++ b/setup/hiero/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -1,6 +1,6 @@ 991 - //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + //10.11.0.184/171001_ftrack/tgbvfx/editorial/hiero/workspace/ 1 True 3 diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml similarity index 99% rename from setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml rename to setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml index e24a4dbe4e..690820c788 100644 --- a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml +++ b/setup/hiero/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -1,6 +1,6 @@ 991 - //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + //10.11.0.184/171001_ftrack/tgbvfx/editorial/hiero/workspace/ 1 True 3 diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py deleted file mode 100644 index e5c5729e2c..0000000000 --- a/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py +++ /dev/null @@ -1,19 +0,0 @@ -import traceback - -# activate nukestudio from pype -import avalon.api -import pype.hosts.nukestudio -avalon.api.install(pype.hosts.nukestudio) - -try: - __import__("pype.hosts.nukestudio") - __import__("pyblish") - -except ImportError as e: - print traceback.format_exc() - print("pyblish: Could not load integration: %s " % e) - -else: - # Setup integration - import pype.hosts.nukestudio.lib - pype.hosts.nukestudio.lib.setup()