From bbe21518a1ef46715064982f8c88b58b11d9f489 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 27 Apr 2020 16:49:55 +0200 Subject: [PATCH 01/40] feat(celaction): adding prelaunch --- pype/hooks/celaction/prelaunch.py | 91 +++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 pype/hooks/celaction/prelaunch.py diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py new file mode 100644 index 0000000000..4ac0baca65 --- /dev/null +++ b/pype/hooks/celaction/prelaunch.py @@ -0,0 +1,91 @@ +import logging +import os +import _winreg +from pype.lib import PypeHook +from pypeapp import Logger + +log = logging.getLogger(__name__) + + +class CelactionPrelounchHook(PypeHook): + """ + This hook will check if current workfile path has Unreal + project inside. IF not, it initialize it and finally it pass + path to the project by environment variable to Unreal launcher + shell script. + """ + + 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 + asset = env["AVALON_ASSET"] + task = env["AVALON_TASK"] + workdir = env["AVALON_WORKDIR"] + engine_version = env["AVALON_APP_NAME"].split("_")[-1] + project_name = f"{asset}_{task}" + + project_path = os.path.join(workdir, project_name) + + self.log.info((f"{self.signature} requested UE4 version: " + f"[ {engine_version} ]")) + + os.makedirs(project_path, exist_ok=True) + + project_file = os.path.join(project_path, f"{project_name}.uproject") + env["PYPE_UNREAL_PROJECT_FILE"] = project_file + + ########################## + # setting output parameters + path = r"Software\CelAction\CelAction2D\User Settings" + _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) + hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, + r"Software\CelAction\CelAction2D\User Settings", + 0, _winreg.KEY_ALL_ACCESS) + + # TODO: change to root path and pyblish standalone to premiere way + root_path = os.getenv("PIPELINE_ROOT", os.path.dirname(__file__)) + path = os.path.join(root_path, "launchers", "pyblish_standalone.bat") + + _winreg.SetValueEx(hKey, "SubmitAppTitle", 0, _winreg.REG_SZ, path) + + parameters = " --path \"*SCENE*\" -d chunk *CHUNK* -d start *START*" + parameters += " -d end *END* -d x *X* -d y *Y* -rh celaction" + parameters += " -8 -d progpath \"*PROGPATH*\"" + _winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, _winreg.REG_SZ, + parameters) + + # setting resolution parameters + path = r"Software\CelAction\CelAction2D\User Settings\Dialogs" + path += r"\SubmitOutput" + _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) + hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, + _winreg.KEY_ALL_ACCESS) + _winreg.SetValueEx(hKey, "SaveScene", 0, _winreg.REG_DWORD, 1) + _winreg.SetValueEx(hKey, "CustomX", 0, _winreg.REG_DWORD, 1920) + _winreg.SetValueEx(hKey, "CustomY", 0, _winreg.REG_DWORD, 1080) + + # making sure message dialogs don't appear when overwriting + path = r"Software\CelAction\CelAction2D\User Settings\Messages" + path += r"\OverwriteScene" + _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) + hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, + _winreg.KEY_ALL_ACCESS) + _winreg.SetValueEx(hKey, "Result", 0, _winreg.REG_DWORD, 6) + _winreg.SetValueEx(hKey, "Valid", 0, _winreg.REG_DWORD, 1) + + path = r"Software\CelAction\CelAction2D\User Settings\Messages" + path += r"\SceneSaved" + _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) + hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, + _winreg.KEY_ALL_ACCESS) + _winreg.SetValueEx(hKey, "Result", 0, _winreg.REG_DWORD, 1) + _winreg.SetValueEx(hKey, "Valid", 0, _winreg.REG_DWORD, 1) + return True From 1489e64077ec916b56fe62139dfd4c5d228f46b9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Apr 2020 11:38:54 +0200 Subject: [PATCH 02/40] feat(celaction): kick off integration --- pype/hooks/celaction/prelaunch.py | 15 ++++++--------- res/app_icons/celaction_local.png | Bin 0 -> 40783 bytes res/app_icons/celaction_remotel.png | Bin 0 -> 36400 bytes 3 files changed, 6 insertions(+), 9 deletions(-) create mode 100644 res/app_icons/celaction_local.png create mode 100644 res/app_icons/celaction_remotel.png diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 4ac0baca65..a0adc26fb3 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -7,7 +7,7 @@ from pypeapp import Logger log = logging.getLogger(__name__) -class CelactionPrelounchHook(PypeHook): +class CelactionPrelaunchHook(PypeHook): """ This hook will check if current workfile path has Unreal project inside. IF not, it initialize it and finally it pass @@ -29,18 +29,14 @@ class CelactionPrelounchHook(PypeHook): asset = env["AVALON_ASSET"] task = env["AVALON_TASK"] workdir = env["AVALON_WORKDIR"] - engine_version = env["AVALON_APP_NAME"].split("_")[-1] project_name = f"{asset}_{task}" - project_path = os.path.join(workdir, project_name) + self.log.info(f"{self.signature}") - self.log.info((f"{self.signature} requested UE4 version: " - f"[ {engine_version} ]")) + os.makedirs(workdir, exist_ok=True) - os.makedirs(project_path, exist_ok=True) - - project_file = os.path.join(project_path, f"{project_name}.uproject") - env["PYPE_UNREAL_PROJECT_FILE"] = project_file + project_file = os.path.join(workdir, f"{project_name}.scn") + env["PYPE_CELACTION_PROJECT_FILE"] = project_file ########################## # setting output parameters @@ -88,4 +84,5 @@ class CelactionPrelounchHook(PypeHook): _winreg.KEY_ALL_ACCESS) _winreg.SetValueEx(hKey, "Result", 0, _winreg.REG_DWORD, 1) _winreg.SetValueEx(hKey, "Valid", 0, _winreg.REG_DWORD, 1) + return True diff --git a/res/app_icons/celaction_local.png b/res/app_icons/celaction_local.png new file mode 100644 index 0000000000000000000000000000000000000000..3a8abe6dbc07316d40c3a57834fb97925f2045ab GIT binary patch literal 40783 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 literal 0 HcmV?d00001 diff --git a/res/app_icons/celaction_remotel.png b/res/app_icons/celaction_remotel.png new file mode 100644 index 0000000000000000000000000000000000000000..320e8173eb90df65e0363f8784e4f034da0c76b8 GIT binary patch literal 36400 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 Date: Tue, 28 Apr 2020 18:11:53 +0200 Subject: [PATCH 03/40] fix(celaction): _winreg to winreg and additional prints --- pype/hooks/celaction/prelaunch.py | 55 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index a0adc26fb3..af48be350c 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -1,6 +1,6 @@ import logging import os -import _winreg +import winreg from pype.lib import PypeHook from pypeapp import Logger @@ -30,59 +30,64 @@ class CelactionPrelaunchHook(PypeHook): task = env["AVALON_TASK"] workdir = env["AVALON_WORKDIR"] project_name = f"{asset}_{task}" + version = "v001" self.log.info(f"{self.signature}") os.makedirs(workdir, exist_ok=True) + self.log.info(f"Work dir is: `{workdir}`") - project_file = os.path.join(workdir, f"{project_name}.scn") + project_file = os.path.join(workdir, f"{project_name}_{version}.scn") env["PYPE_CELACTION_PROJECT_FILE"] = project_file + self.log.info(f"Workfile is: `{project_file}`") + ########################## # setting output parameters path = r"Software\CelAction\CelAction2D\User Settings" - _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) - hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, - r"Software\CelAction\CelAction2D\User Settings", - 0, _winreg.KEY_ALL_ACCESS) + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + "Software\\CelAction\\CelAction2D\\User Settings", 0, + winreg.KEY_ALL_ACCESS) # TODO: change to root path and pyblish standalone to premiere way root_path = os.getenv("PIPELINE_ROOT", os.path.dirname(__file__)) path = os.path.join(root_path, "launchers", "pyblish_standalone.bat") - _winreg.SetValueEx(hKey, "SubmitAppTitle", 0, _winreg.REG_SZ, path) + winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, path) parameters = " --path \"*SCENE*\" -d chunk *CHUNK* -d start *START*" parameters += " -d end *END* -d x *X* -d y *Y* -rh celaction" parameters += " -8 -d progpath \"*PROGPATH*\"" - _winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, _winreg.REG_SZ, - parameters) + winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, + parameters) # setting resolution parameters path = r"Software\CelAction\CelAction2D\User Settings\Dialogs" path += r"\SubmitOutput" - _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) - hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, - _winreg.KEY_ALL_ACCESS) - _winreg.SetValueEx(hKey, "SaveScene", 0, _winreg.REG_DWORD, 1) - _winreg.SetValueEx(hKey, "CustomX", 0, _winreg.REG_DWORD, 1920) - _winreg.SetValueEx(hKey, "CustomY", 0, _winreg.REG_DWORD, 1080) + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, + winreg.KEY_ALL_ACCESS) + winreg.SetValueEx(hKey, "SaveScene", 0, winreg.REG_DWORD, 1) + winreg.SetValueEx(hKey, "CustomX", 0, winreg.REG_DWORD, 1920) + winreg.SetValueEx(hKey, "CustomY", 0, winreg.REG_DWORD, 1080) # making sure message dialogs don't appear when overwriting path = r"Software\CelAction\CelAction2D\User Settings\Messages" path += r"\OverwriteScene" - _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) - hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, - _winreg.KEY_ALL_ACCESS) - _winreg.SetValueEx(hKey, "Result", 0, _winreg.REG_DWORD, 6) - _winreg.SetValueEx(hKey, "Valid", 0, _winreg.REG_DWORD, 1) + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, + winreg.KEY_ALL_ACCESS) + winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 6) + winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1) path = r"Software\CelAction\CelAction2D\User Settings\Messages" path += r"\SceneSaved" - _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, path) - hKey = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, path, 0, - _winreg.KEY_ALL_ACCESS) - _winreg.SetValueEx(hKey, "Result", 0, _winreg.REG_DWORD, 1) - _winreg.SetValueEx(hKey, "Valid", 0, _winreg.REG_DWORD, 1) + winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) + hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, + winreg.KEY_ALL_ACCESS) + winreg.SetValueEx(hKey, "Result", 0, winreg.REG_DWORD, 1) + winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1) return True From 7540293c04a02bbab738fcda878a5a1a5e9d351b Mon Sep 17 00:00:00 2001 From: MIlan Kolar Date: Wed, 29 Apr 2020 17:21:13 +0100 Subject: [PATCH 04/40] wip celaction --- pype/hooks/celaction/prelaunch.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index af48be350c..496a7265f2 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -26,8 +26,10 @@ class CelactionPrelaunchHook(PypeHook): def execute(self, *args, env: dict = None) -> bool: if not env: env = os.environ + project = env["AVALON_PROJECT"] asset = env["AVALON_ASSET"] task = env["AVALON_TASK"] + app = "pype_publish_standalone" workdir = env["AVALON_WORKDIR"] project_name = f"{asset}_{task}" version = "v001" @@ -52,16 +54,31 @@ class CelactionPrelaunchHook(PypeHook): winreg.KEY_ALL_ACCESS) # TODO: change to root path and pyblish standalone to premiere way - root_path = os.getenv("PIPELINE_ROOT", os.path.dirname(__file__)) - path = os.path.join(root_path, "launchers", "pyblish_standalone.bat") + pype_root_path = os.getenv("PYPE_ROOT") + path = os.path.join(pype_root_path, + "pype.bat") + winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, path) - parameters = " --path \"*SCENE*\" -d chunk *CHUNK* -d start *START*" - parameters += " -d end *END* -d x *X* -d y *Y* -rh celaction" - parameters += " -8 -d progpath \"*PROGPATH*\"" + parameters = [ + "launch", + f"--app {app}", + f"--project {project}", + f"--asset {asset}", + f"--task {task}", + "--currentFile \"*SCENE*\"", + "--chunk *CHUNK*", + "--frameStart *START*", + "--frameEnd *END*", + "--resolutionWide *X*", + "--resolutionHeight *Y*", + "--registerHost celaction", + "-8", + "--programPath \"\'*PROGPATH*\'\"" + ] winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, - parameters) + " ".join(parameters)) # setting resolution parameters path = r"Software\CelAction\CelAction2D\User Settings\Dialogs" From 49746f211c3f4654c51a000b7cd1cc94444a99ca Mon Sep 17 00:00:00 2001 From: MIlan Kolar Date: Wed, 29 Apr 2020 17:27:22 +0100 Subject: [PATCH 05/40] change of celaciton publish name --- pype/hooks/celaction/prelaunch.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 496a7265f2..77d074897d 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -29,7 +29,7 @@ class CelactionPrelaunchHook(PypeHook): project = env["AVALON_PROJECT"] asset = env["AVALON_ASSET"] task = env["AVALON_TASK"] - app = "pype_publish_standalone" + app = "celaction_publish" workdir = env["AVALON_WORKDIR"] project_name = f"{asset}_{task}" version = "v001" @@ -74,8 +74,7 @@ class CelactionPrelaunchHook(PypeHook): "--resolutionWide *X*", "--resolutionHeight *Y*", "--registerHost celaction", - "-8", - "--programPath \"\'*PROGPATH*\'\"" + "--programPath \"'*PROGPATH*'\"" ] winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, " ".join(parameters)) From d99d99ff024879817c8f5c25bbc7447a2262743b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 29 Apr 2020 18:31:44 +0200 Subject: [PATCH 06/40] feat(celaction): adding custom publishing script --- pype/celaction/publish.py | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 pype/celaction/publish.py diff --git a/pype/celaction/publish.py b/pype/celaction/publish.py new file mode 100644 index 0000000000..2e7d993a60 --- /dev/null +++ b/pype/celaction/publish.py @@ -0,0 +1,57 @@ +import os +import sys +import pype +import importlib +import pyblish.api +import pyblish.util +import avalon.api +from avalon.tools import publish +from pypeapp import Logger + +log = Logger().get_logger(__name__) + + +def main(env): + # Registers pype's Global pyblish plugins + pype.install() + + # Register Host (and it's pyblish plugins) + host_name = env["AVALON_APP"] + # TODO not sure if use "pype." or "avalon." for host import + host_import_str = f"avalon.{host_name}" + + try: + host_module = importlib.import_module(host_import_str) + except ModuleNotFoundError: + log.error(( + f"Host \"{host_name}\" can't be imported." + f" Import string \"{host_import_str}\" failed." + )) + return False + + avalon.api.install(host_module) + + # Register additional paths + addition_paths_str = env.get("PUBLISH_PATHS") or "" + addition_paths = addition_paths_str.split(os.pathsep) + for path in addition_paths: + path = os.path.normpath(path) + if not os.path.exists(path): + continue + + pyblish.api.register_plugin_path(path) + + # Register project specific plugins + project_name = os.environ["AVALON_PROJECT"] + project_plugins_paths = env.get("PYPE_PROJECT_PLUGINS") or "" + for path in project_plugins_paths.split(os.pathsep): + plugin_path = os.path.join(path, project_name, "plugins") + if os.path.exists(plugin_path): + pyblish.api.register_plugin_path(plugin_path) + + return publish.show() + + +if __name__ == "__main__": + result = main(os.environ) + sys.exit(not bool(result)) From 6c979db2cd4848e16e74fc94e5550f2b1bbae383 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 29 Apr 2020 19:51:29 +0200 Subject: [PATCH 07/40] feat(celaction): publishing cli --- pype/celaction/__init__.py | 0 pype/celaction/cli.py | 123 ++++++++++++++++++++++++++++++ pype/hooks/celaction/prelaunch.py | 6 +- 3 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 pype/celaction/__init__.py create mode 100644 pype/celaction/cli.py diff --git a/pype/celaction/__init__.py b/pype/celaction/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/celaction/cli.py b/pype/celaction/cli.py new file mode 100644 index 0000000000..d001f0bf24 --- /dev/null +++ b/pype/celaction/cli.py @@ -0,0 +1,123 @@ +import sys +import argparse +import os +import copy +from avalon import io +from pypeapp import execute, Logger + +log = Logger().get_logger("Celaction_cli_publisher") + +publish_host = "celaction" + +CURRENT_DIR = os.path.dirname(__file__) +PACKAGE_DIR = os.path.dirname(CURRENT_DIR) +PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, publish_host, "publish") +PUBLISH_SCRIPT_PATH = os.path.join(CURRENT_DIR, "publish.py") + +PUBLISH_PATHS = [ + PUBLISH_PATH, + os.path.join(PLUGINS_DIR, "ftrack", "publish") +] + + +def cli(): + parser = argparse.ArgumentParser(prog="celaction_publish") + + parser.add_argument("--currentFile", + help="Pass file to Context as `currentFile`") + + parser.add_argument("--chunk", + help=("Render chanks on farm")) + + parser.add_argument("--frameStart", + help=("Start of frame range")) + + parser.add_argument("--frameEnd", + help=("End of frame range")) + + parser.add_argument("--resolutionWidth", + help=("Width of resolution")) + + parser.add_argument("--resolutionHeight", + help=("Height of resolution")) + + parser.add_argument("--programDir", + help=("Directory with celaction program installation")) + + return parser.parse_args(sys.argv[1:]).__dict__ + + +def publish(data): + """Triggers publishing script in subprocess. + + """ + try: + publish_env = _prepare_publish_environments( + data + ) + except Exception as exc: + log.warning( + "Failed to prepare environments for publishing.", + exc_info=True + ) + Exception(str(exc)) + + log.info("Pyblish is running") + try: + # Trigger subprocess + # QUESTION should we check returncode? + returncode = execute( + [sys.executable, PUBLISH_SCRIPT_PATH], + env=publish_env + ) + + # Check if output file exists + if returncode != 0: + Exception("Publishing failed") + + log.info("Pyblish have stopped") + + return True + + except Exception: + log.warning("Publishing failed", exc_info=True) + Exception("Publishing failed") + + +def _prepare_publish_environments(data): + """Prepares environments based on request data.""" + env = copy.deepcopy(os.environ) + + project_name = data["project"] + asset_name = data["asset"] + + project_doc = io.find_one({ + "type": "project" + }) + av_asset = io.find_one({ + "type": "asset", + "name": asset_name + }) + parents = av_asset["data"]["parents"] + hierarchy = "" + if parents: + hierarchy = "/".join(parents) + + env["AVALON_PROJECT"] = project_name + env["AVALON_ASSET"] = asset_name + env["AVALON_TASK"] = data["task"] + env["AVALON_WORKDIR"] = data["workdir"] + env["AVALON_HIERARCHY"] = hierarchy + env["AVALON_PROJECTCODE"] = project_doc["data"].get("code", "") + env["AVALON_APP"] = publish_host + env["AVALON_APP_NAME"] = publish_host + + env["PYBLISH_HOSTS"] = publish_host + env["PUBLISH_PATHS"] = os.pathsep.join(PUBLISH_PATHS) + return env + + +if __name__ == "__main__": + data = cli() + publish(data) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 77d074897d..98860b5eeb 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -56,8 +56,7 @@ class CelactionPrelaunchHook(PypeHook): # TODO: change to root path and pyblish standalone to premiere way pype_root_path = os.getenv("PYPE_ROOT") path = os.path.join(pype_root_path, - "pype.bat") - + "pype.bat") winreg.SetValueEx(hKey, "SubmitAppTitle", 0, winreg.REG_SZ, path) @@ -71,9 +70,8 @@ class CelactionPrelaunchHook(PypeHook): "--chunk *CHUNK*", "--frameStart *START*", "--frameEnd *END*", - "--resolutionWide *X*", + "--resolutionWidth *X*", "--resolutionHeight *Y*", - "--registerHost celaction", "--programPath \"'*PROGPATH*'\"" ] winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, From 4de28c180aea622c5e2029a679d6b1910f42d574 Mon Sep 17 00:00:00 2001 From: MIlan Kolar Date: Wed, 29 Apr 2020 19:43:38 +0100 Subject: [PATCH 08/40] fixing celaction publisher --- pype/celaction/__init__.py | 1 + pype/celaction/cli.py | 109 +++++++++++++++++------------- pype/celaction/publish.py | 57 ---------------- pype/hooks/celaction/prelaunch.py | 2 +- 4 files changed, 63 insertions(+), 106 deletions(-) delete mode 100644 pype/celaction/publish.py diff --git a/pype/celaction/__init__.py b/pype/celaction/__init__.py index e69de29bb2..47e81a9212 100644 --- a/pype/celaction/__init__.py +++ b/pype/celaction/__init__.py @@ -0,0 +1 @@ +kwargs = None diff --git a/pype/celaction/cli.py b/pype/celaction/cli.py index d001f0bf24..bbce6247fc 100644 --- a/pype/celaction/cli.py +++ b/pype/celaction/cli.py @@ -1,9 +1,21 @@ -import sys -import argparse import os +import sys import copy +import argparse +import importlib + from avalon import io +import avalon.api +from avalon.tools import publish + +import pyblish.api +import pyblish.util + from pypeapp import execute, Logger +import pype +import pype.celaction + + log = Logger().get_logger("Celaction_cli_publisher") @@ -13,7 +25,6 @@ CURRENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(CURRENT_DIR) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, publish_host, "publish") -PUBLISH_SCRIPT_PATH = os.path.join(CURRENT_DIR, "publish.py") PUBLISH_PATHS = [ PUBLISH_PATH, @@ -45,53 +56,19 @@ def cli(): parser.add_argument("--programDir", help=("Directory with celaction program installation")) - return parser.parse_args(sys.argv[1:]).__dict__ -def publish(data): - """Triggers publishing script in subprocess. - - """ - try: - publish_env = _prepare_publish_environments( - data - ) - except Exception as exc: - log.warning( - "Failed to prepare environments for publishing.", - exc_info=True - ) - Exception(str(exc)) - - log.info("Pyblish is running") - try: - # Trigger subprocess - # QUESTION should we check returncode? - returncode = execute( - [sys.executable, PUBLISH_SCRIPT_PATH], - env=publish_env - ) - - # Check if output file exists - if returncode != 0: - Exception("Publishing failed") - - log.info("Pyblish have stopped") - - return True - - except Exception: - log.warning("Publishing failed", exc_info=True) - Exception("Publishing failed") + pype.celaction.kwargs = parser.parse_args(sys.argv[1:]).__dict__ -def _prepare_publish_environments(data): +def _prepare_publish_environments(): """Prepares environments based on request data.""" env = copy.deepcopy(os.environ) - project_name = data["project"] - asset_name = data["asset"] + project_name = os.getenv("AVALON_PROJECT") + asset_name = os.getenv("AVALON_ASSET") + io.install() project_doc = io.find_one({ "type": "project" }) @@ -106,18 +83,54 @@ def _prepare_publish_environments(data): env["AVALON_PROJECT"] = project_name env["AVALON_ASSET"] = asset_name - env["AVALON_TASK"] = data["task"] - env["AVALON_WORKDIR"] = data["workdir"] + env["AVALON_TASK"] = os.getenv("AVALON_TASK") + env["AVALON_WORKDIR"] = os.getenv("AVALON_WORKDIR") env["AVALON_HIERARCHY"] = hierarchy env["AVALON_PROJECTCODE"] = project_doc["data"].get("code", "") env["AVALON_APP"] = publish_host env["AVALON_APP_NAME"] = publish_host env["PYBLISH_HOSTS"] = publish_host - env["PUBLISH_PATHS"] = os.pathsep.join(PUBLISH_PATHS) - return env + + os.environ.update(env) + +def _main(): + # Registers pype's Global pyblish plugins + pype.install() + + host_import_str = f"pype.{publish_host}" + + try: + host_module = importlib.import_module(host_import_str) + except ModuleNotFoundError: + log.error(( + f"Host \"{publish_host}\" can't be imported." + f" Import string \"{host_import_str}\" failed." + )) + return False + + avalon.api.install(host_module) + + for path in PUBLISH_PATH: + path = os.path.normpath(path) + if not os.path.exists(path): + continue + + pyblish.api.register_plugin_path(path) + + # Register project specific plugins + project_name = os.environ["AVALON_PROJECT"] + project_plugins_paths = os.getenv("PYPE_PROJECT_PLUGINS", "") + for path in project_plugins_paths.split(os.pathsep): + plugin_path = os.path.join(path, project_name, "plugins") + if os.path.exists(plugin_path): + pyblish.api.register_plugin_path(plugin_path) + + return publish.show() if __name__ == "__main__": - data = cli() - publish(data) + cli() + _prepare_publish_environments() + result = _main() + sys.exit(not bool(result)) diff --git a/pype/celaction/publish.py b/pype/celaction/publish.py deleted file mode 100644 index 2e7d993a60..0000000000 --- a/pype/celaction/publish.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import sys -import pype -import importlib -import pyblish.api -import pyblish.util -import avalon.api -from avalon.tools import publish -from pypeapp import Logger - -log = Logger().get_logger(__name__) - - -def main(env): - # Registers pype's Global pyblish plugins - pype.install() - - # Register Host (and it's pyblish plugins) - host_name = env["AVALON_APP"] - # TODO not sure if use "pype." or "avalon." for host import - host_import_str = f"avalon.{host_name}" - - try: - host_module = importlib.import_module(host_import_str) - except ModuleNotFoundError: - log.error(( - f"Host \"{host_name}\" can't be imported." - f" Import string \"{host_import_str}\" failed." - )) - return False - - avalon.api.install(host_module) - - # Register additional paths - addition_paths_str = env.get("PUBLISH_PATHS") or "" - addition_paths = addition_paths_str.split(os.pathsep) - for path in addition_paths: - path = os.path.normpath(path) - if not os.path.exists(path): - continue - - pyblish.api.register_plugin_path(path) - - # Register project specific plugins - project_name = os.environ["AVALON_PROJECT"] - project_plugins_paths = env.get("PYPE_PROJECT_PLUGINS") or "" - for path in project_plugins_paths.split(os.pathsep): - plugin_path = os.path.join(path, project_name, "plugins") - if os.path.exists(plugin_path): - pyblish.api.register_plugin_path(plugin_path) - - return publish.show() - - -if __name__ == "__main__": - result = main(os.environ) - sys.exit(not bool(result)) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 98860b5eeb..a31d54e920 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -72,7 +72,7 @@ class CelactionPrelaunchHook(PypeHook): "--frameEnd *END*", "--resolutionWidth *X*", "--resolutionHeight *Y*", - "--programPath \"'*PROGPATH*'\"" + "--programDir \"'*PROGPATH*'\"" ] winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, " ".join(parameters)) From 47ecf5d331d606d461dfd03f8616bc4a7aeb8e43 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 29 Apr 2020 20:47:36 +0200 Subject: [PATCH 09/40] fix(celaction): fix code style --- pype/celaction/cli.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pype/celaction/cli.py b/pype/celaction/cli.py index bbce6247fc..499f579fde 100644 --- a/pype/celaction/cli.py +++ b/pype/celaction/cli.py @@ -11,12 +11,11 @@ from avalon.tools import publish import pyblish.api import pyblish.util -from pypeapp import execute, Logger +from pypeapp import Logger import pype import pype.celaction - log = Logger().get_logger("Celaction_cli_publisher") publish_host = "celaction" @@ -56,8 +55,6 @@ def cli(): parser.add_argument("--programDir", help=("Directory with celaction program installation")) - - pype.celaction.kwargs = parser.parse_args(sys.argv[1:]).__dict__ @@ -94,7 +91,11 @@ def _prepare_publish_environments(): os.environ.update(env) -def _main(): + +def main(): + # prepare all environments + _prepare_publish_environments() + # Registers pype's Global pyblish plugins pype.install() @@ -120,7 +121,7 @@ def _main(): # Register project specific plugins project_name = os.environ["AVALON_PROJECT"] - project_plugins_paths = os.getenv("PYPE_PROJECT_PLUGINS", "") + project_plugins_paths = os.getenv("PYPE_PROJECT_PLUGINS", "") for path in project_plugins_paths.split(os.pathsep): plugin_path = os.path.join(path, project_name, "plugins") if os.path.exists(plugin_path): @@ -131,6 +132,5 @@ def _main(): if __name__ == "__main__": cli() - _prepare_publish_environments() - result = _main() + result = main() sys.exit(not bool(result)) From 018dae66353c6453727c50ddc029b3c97a2ef588 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 29 Apr 2020 20:20:45 +0100 Subject: [PATCH 10/40] adding plugins for celaction --- pype/celaction/cli.py | 8 +- .../append_celaction_ftrack_asset_name.py | 19 ++ .../publish/append_celaction_ftrack_data.py | 46 +++ .../celaction/publish/collect_audio.py | 39 +++ .../publish/collect_celaction_render.py | 31 ++ .../publish/collect_celaction_scene.py | 10 + .../plugins/celaction/publish/collect_data.py | 18 ++ .../celaction/publish/collect_kwargs.py | 17 ++ .../publish/extract_celaction_deadline.py | 109 +++++++ .../celaction/publish/integrate_version_up.py | 69 +++++ .../celaction/publish/submit_publish_job.py | 283 ++++++++++++++++++ .../publish/validate_celaction_scene_path.py | 62 ++++ 12 files changed, 708 insertions(+), 3 deletions(-) create mode 100644 pype/plugins/celaction/publish/append_celaction_ftrack_asset_name.py create mode 100644 pype/plugins/celaction/publish/append_celaction_ftrack_data.py create mode 100644 pype/plugins/celaction/publish/collect_audio.py create mode 100644 pype/plugins/celaction/publish/collect_celaction_render.py create mode 100644 pype/plugins/celaction/publish/collect_celaction_scene.py create mode 100644 pype/plugins/celaction/publish/collect_data.py create mode 100644 pype/plugins/celaction/publish/collect_kwargs.py create mode 100644 pype/plugins/celaction/publish/extract_celaction_deadline.py create mode 100644 pype/plugins/celaction/publish/integrate_version_up.py create mode 100644 pype/plugins/celaction/publish/submit_publish_job.py create mode 100644 pype/plugins/celaction/publish/validate_celaction_scene_path.py diff --git a/pype/celaction/cli.py b/pype/celaction/cli.py index 499f579fde..ddbe2ec0c0 100644 --- a/pype/celaction/cli.py +++ b/pype/celaction/cli.py @@ -85,7 +85,7 @@ def _prepare_publish_environments(): env["AVALON_HIERARCHY"] = hierarchy env["AVALON_PROJECTCODE"] = project_doc["data"].get("code", "") env["AVALON_APP"] = publish_host - env["AVALON_APP_NAME"] = publish_host + env["AVALON_APP_NAME"] = "celaction_local" env["PYBLISH_HOSTS"] = publish_host @@ -110,13 +110,15 @@ def main(): )) return False - avalon.api.install(host_module) + # avalon.api.install(host_module) - for path in PUBLISH_PATH: + for path in PUBLISH_PATHS: path = os.path.normpath(path) + if not os.path.exists(path): continue + log.info(f"Registering path: {path}") pyblish.api.register_plugin_path(path) # Register project specific plugins diff --git a/pype/plugins/celaction/publish/append_celaction_ftrack_asset_name.py b/pype/plugins/celaction/publish/append_celaction_ftrack_asset_name.py new file mode 100644 index 0000000000..734bdffe39 --- /dev/null +++ b/pype/plugins/celaction/publish/append_celaction_ftrack_asset_name.py @@ -0,0 +1,19 @@ +import pyblish.api + + +class AppendCelactionFtrackAssetName(pyblish.api.InstancePlugin): + """ Appending "ftrackAssetName" """ + + label = "Ftrack Asset Name" + order = pyblish.api.CollectorOrder + 0.1 + + def process(self, instance): + + # skipping if not launched from ftrack + if "ftrackData" not in instance.context.data: + return + + ftrack_data = instance.context.data["ftrackData"] + + asset_name = ftrack_data["Task"]["name"] + instance.data["ftrackAssetName"] = asset_name diff --git a/pype/plugins/celaction/publish/append_celaction_ftrack_data.py b/pype/plugins/celaction/publish/append_celaction_ftrack_data.py new file mode 100644 index 0000000000..fab3ae8b1a --- /dev/null +++ b/pype/plugins/celaction/publish/append_celaction_ftrack_data.py @@ -0,0 +1,46 @@ +import pyblish.api +from bait.ftrack.query_runner import QueryRunner + + +class AppendCelactionFtrackAudio(pyblish.api.ContextPlugin): + + label = "Ftrack Audio" + order = pyblish.api.ExtractorOrder + + def process(self, context): + + if context.data.get("audio", ''): + self.log.info('Audio data are already collected') + self.log.info('Audio: {}'.format(context.data.get("audio", ''))) + return + + runner = QueryRunner(context.data['ftrackSession']) + + audio_file = runner.get_audio_file_for_shot( + context.data['ftrackData']["Shot"]["id"]) + + if audio_file: + context.data["audio"] = { + 'filename': audio_file, + 'enabled': True + } + else: + self.log.warning("Couldn't find any audio file on Ftrack.") + + +class AppendCelactionFtrackData(pyblish.api.InstancePlugin): + """ Appending ftrack component and asset type data """ + + families = ["img.*", "mov.*"] + # offset to piggy back from default collectors + order = pyblish.api.CollectorOrder + 0.1 + + def process(self, instance): + + # ftrack data + if not instance.context.has_data("ftrackData"): + return + + instance.data["ftrackComponents"] = {} + asset_type = instance.data["family"].split(".")[0] + instance.data["ftrackAssetType"] = asset_type diff --git a/pype/plugins/celaction/publish/collect_audio.py b/pype/plugins/celaction/publish/collect_audio.py new file mode 100644 index 0000000000..bf4e1f47f3 --- /dev/null +++ b/pype/plugins/celaction/publish/collect_audio.py @@ -0,0 +1,39 @@ +import pyblish.api +from bait.paths import get_output_path +import os + + +class AppendCelactionAudio(pyblish.api.ContextPlugin): + + label = "Pype Audio" + order = pyblish.api.CollectorOrder + 0.1 + + def process(self, context): + self.log.info('Collecting Audio Data') + version = context.data('version') if context.has_data('version') else 1 + + task_id = context.data["ftrackData"]["Task"]["id"] + + component_name = context.data["ftrackData"]['Shot']['name'] + version = context.data["version"] + + publish_path = get_output_path( + task_id, component_name, version, "mov").split('/')[0:-4] + + self.log.info('publish_path: {}'.format(publish_path)) + + audio_file = '/'.join(publish_path + [ + 'audio', + 'audioMain', + component_name + '_audioMain_v001.wav' + ]) + + if os.path.exists(audio_file): + context.data["audio"] = { + 'filename': audio_file, + 'enabled': True + } + self.log.info( + 'audio_file: {}, has been added to context'.format(audio_file)) + else: + self.log.warning("Couldn't find any audio file on Ftrack.") diff --git a/pype/plugins/celaction/publish/collect_celaction_render.py b/pype/plugins/celaction/publish/collect_celaction_render.py new file mode 100644 index 0000000000..e6761633f9 --- /dev/null +++ b/pype/plugins/celaction/publish/collect_celaction_render.py @@ -0,0 +1,31 @@ +import os + +import pyblish.api + + +class CollectCelactionRender(pyblish.api.ContextPlugin): + """ Adds the celaction render instances """ + + order = pyblish.api.CollectorOrder + 0.1 + + def process(self, context): + + # scene render + scene_file = os.path.basename(context.data["currentFile"]) + scene_name, _ = os.path.splitext(scene_file) + component_name = scene_name.split(".")[0] + + instance = context.create_instance(name=component_name) + instance.data["family"] = "render" + instance.data["label"] = "{} - remote".format(component_name) + # instance.data["families"] = ["deadline", "remote", "img"] + instance.data["families"] = ["render", "img"] + instance.data["managed_location"] = False + + # getting instance state + instance.data["publish"] = False + + data = context.data("kwargs")["data"] + + for item in data: + instance.set_data(item, value=data[item]) diff --git a/pype/plugins/celaction/publish/collect_celaction_scene.py b/pype/plugins/celaction/publish/collect_celaction_scene.py new file mode 100644 index 0000000000..006edcb1c3 --- /dev/null +++ b/pype/plugins/celaction/publish/collect_celaction_scene.py @@ -0,0 +1,10 @@ +import pyblish.api + + +class CollectCelactionScene(pyblish.api.ContextPlugin): + """ Converts the path flag value to the current file in the context. """ + + order = pyblish.api.CollectorOrder + + def process(self, context): + context.data['ftrackStatus'] = "Ready" diff --git a/pype/plugins/celaction/publish/collect_data.py b/pype/plugins/celaction/publish/collect_data.py new file mode 100644 index 0000000000..ce416403cc --- /dev/null +++ b/pype/plugins/celaction/publish/collect_data.py @@ -0,0 +1,18 @@ +import os +import pyblish.api +import pype.celaction + + +class CollectData(pyblish.api.Collector): + """Collects data passed from via CLI""" + + order = pyblish.api.Collector.order - 0.1 + + def process(self, context): + self.log.info("Adding data from command-line into Context..") + + kwargs = pype.celaction.kwargs.copy() + + for key, value in kwargs.items(): + self.log.info("%s = %s" % (key, value)) + context.set_data(key, value) diff --git a/pype/plugins/celaction/publish/collect_kwargs.py b/pype/plugins/celaction/publish/collect_kwargs.py new file mode 100644 index 0000000000..b2e6d97b8b --- /dev/null +++ b/pype/plugins/celaction/publish/collect_kwargs.py @@ -0,0 +1,17 @@ +import pyblish.api +import pype.celaction + + +class CollectKwargs(pyblish.api.Collector): + """ Collects all keyword arguments passed from the terminal """ + + order = pyblish.api.Collector.order - 0.1 + + def process(self, context): + kwargs = pype.celaction.kwargs.copy() + + self.log.info("Converting nested lists to dict: %s" % kwargs) + kwargs["data"] = dict(kwargs.get("data") or []) + + self.log.info("Storing kwargs: %s" % kwargs) + context.set_data("kwargs", kwargs) diff --git a/pype/plugins/celaction/publish/extract_celaction_deadline.py b/pype/plugins/celaction/publish/extract_celaction_deadline.py new file mode 100644 index 0000000000..3deb612fd5 --- /dev/null +++ b/pype/plugins/celaction/publish/extract_celaction_deadline.py @@ -0,0 +1,109 @@ +import os + +import pyblish.api +import pyblish_standalone +import clique +import requests +from bait.deadline import get_render_settings, get_deadline_data, format_frames +from bait.paths import get_output_path + + +class ExtractCelactionDeadline(pyblish.api.InstancePlugin): + + label = 'Deadline' + families = ['render'] + order = pyblish.api.ExtractorOrder + + def process(self, instance): + + render_settings = get_render_settings("celaction") + + existing_data = instance.data.get( + "deadlineData", {"job": {}, "plugin": {}} + ) + + task_id = instance.context.data["ftrackData"]["Task"]["id"] + + data = get_deadline_data(render_settings, existing_data) + + filename = os.path.basename(instance.context.data["currentFile"]) + filename_no_ext, ext = os.path.splitext(filename) + + data["job"]["Name"] = filename_no_ext + " - " + instance.data["name"] + data["job"]['Frames'] = format_frames( + instance.data('start'), instance.data('end')) + + # get version data + version = instance.context.data( + 'version') if instance.context.has_data('version') else 1 + + output_path = get_output_path( + task_id, instance.data["name"], version, "png") + output_path = output_path.replace("/", "\\") + + data['job']['Plugin'] = 'CelAction' + data['job']["BatchName"] = filename + data['job']["UserName"] = os.environ['USERNAME'] + data["job"]['OutputFilename0'] = output_path.replace('%04d', '####') + + scene_path = pyblish_standalone.kwargs['path'][0] + scene_path = scene_path.replace("/", "\\") + _, ext = os.path.splitext(scene_path) + + # plugin data + self.log.info(scene_path) + + args = '{}'.format(scene_path) + args += ' -a' + args += ' -8' + args += ' -s ' + args += ' -e ' + args += ' -d {}'.format(os.path.dirname(output_path)) + args += ' -x {}'.format(instance.data('x')) + args += ' -y {}'.format(instance.data('y')) + args += ' -r {}'.format(output_path.replace('.%04d', '')) + args += ' -= AbsoluteFrameNumber=on -= PadDigits=4' + args += ' -= ClearAttachment=on' + + data["plugin"]['StartupDirectory'] = '' + data["plugin"]['Arguments'] = args + + self.log.info(data) + + head = output_path.replace('%04d', '') + tail = ".png" + collection = clique.Collection(head=head, padding=4, tail=tail) + + frame_start = int(instance.data['start']) + frame_end = int(instance.data['end']) + + for frame_no in range(frame_start, frame_end): + collection.add(head + str(frame_no).zfill(4) + tail) + + # instance.data["collection"] = collection + + # adding to instance + instance.set_data('deadlineData2', value=data) + # instance.set_data('deadlineSubmissionJob', value=data) + + payload = { + "JobInfo": data["job"], + "PluginInfo": data["plugin"], + "AuxFiles": [] + } + + url = "{}/api/jobs".format('http://192.168.146.8:8082') + response = requests.post(url, json=payload) + if not response.ok: + raise Exception(response.text) + + # Store output dir for unified publisher (filesequence) + instance.data["outputDir"] = os.path.dirname( + data["job"]['OutputFilename0']) + instance.data["deadlineSubmissionJob"] = response.json() + + instance.context.data['ftrackStatus'] = "Render" + + # creating output path + if not os.path.exists(os.path.dirname(output_path)): + os.makedirs(os.path.dirname(output_path)) diff --git a/pype/plugins/celaction/publish/integrate_version_up.py b/pype/plugins/celaction/publish/integrate_version_up.py new file mode 100644 index 0000000000..77c0d7a88b --- /dev/null +++ b/pype/plugins/celaction/publish/integrate_version_up.py @@ -0,0 +1,69 @@ +import shutil +import os +import re +import pyblish.api + + +class VersionUpScene(pyblish.api.ContextPlugin): + order = pyblish.api.IntegratorOrder + label = 'Version Up Scene' + families = ['scene'] + optional = True + active = True + + def process(self, context): + current_file = context.data.get('currentFile') + v_up = get_version_up(current_file) + self.log.debug('Current file is: {}'.format(current_file)) + self.log.debug('Version up: {}'.format(v_up)) + + shutil.copy2(current_file, v_up) + self.log.info('Scene saved into new version: {}'.format(v_up)) + + +def version_get(string, prefix, suffix=None): + """Extract version information from filenames used by DD (and Weta, apparently) + These are _v# or /v# or .v# where v is a prefix string, in our case + we use "v" for render version and "c" for camera track version. + See the version.py and camera.py plugins for usage.""" + + if string is None: + raise ValueError("Empty version string - no match") + + regex = "[/_.]" + prefix + "\d+" + matches = re.findall(regex, string, re.IGNORECASE) + if not len(matches): + msg = "No \"_" + prefix + "#\" found in \"" + string + "\"" + raise ValueError(msg) + return (matches[-1:][0][1], re.search("\d+", matches[-1:][0]).group()) + + +def version_set(string, prefix, oldintval, newintval): + """Changes version information from filenames used by DD (and Weta, apparently) + These are _v# or /v# or .v# where v is a prefix string, in our case + we use "v" for render version and "c" for camera track version. + See the version.py and camera.py plugins for usage.""" + + regex = "[/_.]" + prefix + "\d+" + matches = re.findall(regex, string, re.IGNORECASE) + if not len(matches): + return "" + + # Filter to retain only version strings with matching numbers + matches = filter(lambda s: int(s[2:]) == oldintval, matches) + + # Replace all version strings with matching numbers + for match in matches: + # use expression instead of expr so 0 prefix does not make octal + fmt = "%%(#)0%dd" % (len(match) - 2) + newfullvalue = match[0] + prefix + str(fmt % {"#": newintval}) + string = re.sub(match, newfullvalue, string) + return string + + +def get_version_up(path): + """ Returns the next version of the path """ + + (prefix, v) = version_get(path, 'v') + v = int(v) + return version_set(path, prefix, v, v + 1) diff --git a/pype/plugins/celaction/publish/submit_publish_job.py b/pype/plugins/celaction/publish/submit_publish_job.py new file mode 100644 index 0000000000..8d73dc245f --- /dev/null +++ b/pype/plugins/celaction/publish/submit_publish_job.py @@ -0,0 +1,283 @@ +import os +import json +import pprint +import re +import requests +import pyblish.api + + +class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): + """Submit image sequence publish jobs to Deadline. + + These jobs are dependent on a deadline job submission prior to this + plug-in. + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable DEADLINE_REST_URL + + Options in instance.data: + - deadlineSubmission (dict, Required): The returned .json + data from the job submission to deadline. + + - outputDir (str, Required): The output directory where the metadata + file should be generated. It's assumed that this will also be + final folder containing the output files. + + - ext (str, Optional): The extension (including `.`) that is required + in the output filename to be picked up for image sequence + publishing. + + - publishJobState (str, Optional): "Active" or "Suspended" + This defaults to "Suspended" + + This requires a "startFrame" and "endFrame" to be present in instance.data + or in context.data. + + """ + + label = "Submit image sequence jobs to Deadline" + order = pyblish.api.IntegratorOrder + 0.2 + + hosts = ["celaction"] + + families = [ + "render", + "deadline" + ] + + def process(self, instance): + + # DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL", + # "http://localhost:8082") + # assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" + + # Get a submission job + + job = instance.data.get("deadlineSubmissionJob") + if not job: + raise RuntimeError("Can't continue without valid deadline " + "submission prior to this plug-in.") + ################ + ft_data = instance.context.data["ftrackData"] + project = ft_data['Project']['name'] + project_code = ft_data['Project']['code'] + projects_path = os.path.dirname(ft_data['Project']['root']) + + data = instance.data.copy() + asset = instance.context.data["ftrackData"]['Shot']['name'] + subset = 'render' + \ + instance.context.data["ftrackData"]['Task']['name'].capitalize() + + state = data.get("publishJobState", "Suspended") + # job_name = "{batch} - {subset} [publish image sequence]".format( + # batch=job["Props"]["Name"], + # subset=subset + # ) + job_name = "{asset} [publish image sequence]".format( + asset=asset + ) + + # Get start/end frame from instance, if not available get from context + context = instance.context + + start = int(instance.data['start']) + end = int(instance.data['end']) + + try: + source = data['source'] + except KeyError: + source = context.data["currentFile"] + + # Write metadata for publish job + render_job = data.pop("deadlineSubmissionJob") + metadata = { + "asset": asset, + "regex": r"^.*\.png", + "subset": subset, + "startFrame": start, + "endFrame": end, + "fps": context.data.get("fps", None), + "families": ["render"], + "source": source, + "user": context.data["user"], + "version": context.data.get('version'), + "audio": context.data["audio"]['filename'], + # Optional metadata (for debugging) + "metadata": { + "instance": data, + "job": job, + "session": fake_avalon_session(project, projects_path) + } + } + + # Ensure output dir exists + output_dir = instance.data["outputDir"] + + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + + for k, v in metadata.items(): + self.log.info(k) + self.log.info(v) + + metadata_filename = "{}_metadata.json".format(subset) + metadata_path = os.path.join(output_dir, metadata_filename) + with open(metadata_path, "w") as f: + json.dump(metadata, f, indent=4, sort_keys=True) + + # Generate the payload for Deadline submission + payload = { + "JobInfo": { + "Plugin": "Python", + "BatchName": job["Props"]["Batch"], + "Name": job_name, + "JobType": "Normal", + "Group": "celaction", + "JobDependency0": job["_id"], + "UserName": os.environ['USERNAME'], + "Comment": instance.context.data.get("comment", ""), + "InitialStatus": "Active" + }, + "PluginInfo": { + "Version": "3.6", + "ScriptFile": r"\\pype\Core\dev\pype-setup\repos\pype-config\pype\scripts\publish_filesequence.py", + "Arguments": '--path "{}"'.format(metadata_path), + "SingleFrameOnly": "True" + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + # Transfer the environment from the original job to this dependent + # job so they use the same environment + environment = fake_env() + environment["AVALON_ASSET"] = asset + environment["AVALON_TASK"] = instance.context.data["ftrackData"]['Task']['name'] + environment["AVALON_PROJECT"] = project + environment["AVALON_PROJECTS"] = projects_path + environment["PYPE_STUDIO_PROJECTS_PUBLISH"] = ft_data['Project']['root'] + environment["PYPE_STUDIO_PROJECTS_RENDER"] = ft_data['Project']['root'] + environment["PYPE_STUDIO_PROJECTS_RESOURCES"] = ft_data['Project']['root'] + environment["PYPE_STUDIO_PROJECTS_WORK"] = ft_data['Project']['root'] + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + # Avoid copied pools and remove secondary pool + payload["JobInfo"]["Pool"] = "animation_2d" + payload["JobInfo"].pop("SecondaryPool", None) + + self.log.info("Submitting..") + # self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + + ################ + ###################### + fake_instance = instance.context.create_instance( + name=(str(instance) + "1")) + + for k, v in data.items(): + self.log.info(k) + fake_instance.data[k] = v + + # fake_instance.data['deadlineData'] = payload + # 'http://192.168.146.8:8082' + url = "{}/api/jobs".format('http://192.168.146.8:8082') + response = requests.post(url, json=payload) + if not response.ok: + raise Exception(response.text) + ###################### + ####################### + + +def fake_avalon_session(project=None, projects_path=None): + return { + "AVALON_APP": "premiere", + "AVALON_APP_VERSION": "2019", + "AVALON_ASSET": "editorial", + "AVALON_CONFIG": "pype", + "AVALON_CONTAINER_ID": "avalon.container", + "AVALON_DB": "Pype", + "AVALON_DEADLINE": "http://192.168.146.8:8082", + "AVALON_DEBUG": "1", + "AVALON_EARLY_ADOPTER": "1", + "AVALON_INSTANCE_ID": "avalon.instance", + "AVALON_LABEL": "Avalon", + "AVALON_LOCATION": "http://127.0.0.1", + "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", + "AVALON_PASSWORD": "secret", + "AVALON_PROJECT": project or "LBB2_dev", + "AVALON_PROJECTS": projects_path or "L:/PYPE_test", + "AVALON_SILO": "editorial", + "AVALON_TASK": "conform", + "AVALON_TIMEOUT": "1000", + "AVALON_USERNAME": "avalon", + "AVALON_WORKDIR": "L:/PYPE_test/episodes/editorial/work/conform", + "schema": "avalon-core:session-1.0" + } + + +def fake_env(): + return { + "AVALON_CONFIG": "pype", + "AVALON_CONTAINER_ID": "avalon.container", + "AVALON_CORE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core", + "AVALON_DB": "Pype", + "AVALON_DB_DATA": "\\\\pype\\Core\\dev\\mongo_db_data", + "AVALON_DEADLINE": "http://192.168.146.8:8082", + "AVALON_DEBUG": "1", + "AVALON_EARLY_ADOPTER": "1", + "AVALON_ENV_NAME": "pype_env", + "AVALON_HIERARCHY": "", + "AVALON_INSTANCE_ID": "avalon.instance", + "AVALON_LABEL": "Avalon", + "AVALON_LAUNCHER": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher", + "AVALON_LOCATION": "http://127.0.0.1", + "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", + "AVALON_MONGO_PORT": "27072", + "AVALON_PASSWORD": "secret", + "AVALON_SCHEMA": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\schema", + "AVALON_SILO": "", + "AVALON_TIMEOUT": "1000", + "AVALON_USERNAME": "avalon", + "AVALON_WORKDIR": "default", + "DEADLINE_PATH": "C:\\Program Files\\Thinkbox\\Deadline10\\bin", + "DEADLINE_REST_URL": "http://192.168.146.8:8082", + "FTRACK_API_KEY": "NGI0ZGU3ZjMtNzNiZC00NGVlLWEwY2EtMzA1OWJlZGM0MjAyOjozZWZmMThjZi04MjkwLTQxMzQtODUwMC03NTZhMGJiZTM2MTA", + "FTRACK_API_USER": "license@clothcatanimation.com", + "FTRACK_SERVER": "https://clothcat2.ftrackapp.com", + "MONGO_DB_ENTRYDB": "Pype", + "MONGO_DB_PASS": "X34vkuwL4wbK9A7X", + "MONGO_DB_USER": "PypeAdmin", + "PATH": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Scripts;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library;;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin\\windows;\\\\pype\\Core\\dev\\pype-setup\\app;\\\\pype\\core\\software\\ffmpeg\\bin;\\\\pype\\Core\\dev\\Applications\\djv\\bin", + "PYBLISHPLUGINPATH": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\plugins\\ftrack\\publish;", + "PYBLISH_BASE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base", + "PYBLISH_HOSTS": "shell", + "PYBLISH_LITE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite", + "PYBLISH_QML": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml", + "PYPE_APP_ROOT": "\\\\pype\\Core\\dev\\pype-setup\\app", + "PYPE_DEBUG": "3", + "PYPE_DEBUG_STDOUT": "0", + "PYPE_SETUP_ROOT": "\\\\pype\\Core\\dev\\pype-setup", + "PYPE_STUDIO_CODE": "CC", + "PYPE_STUDIO_CONFIG": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config", + "PYPE_STUDIO_CORE": "\\\\pype\\Core\\dev", + "PYPE_STUDIO_CORE_MOUNT": "\\\\pype\\Core\\dev", + "PYPE_STUDIO_NAME": "Cloth Cat", + "PYPE_STUDIO_SOFTWARE": "\\\\pype\\Core\\dev\\Applications", + "PYPE_STUDIO_TEMPLATES": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates", + "PYPE_STUDIO_TOOLS": "\\\\pype\\Core\\dev\\production\\tools", + "PYTHONPATH": "\\\\pype\\Core\\dev\\pype-setup;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server", + "PYTHONVERBOSE": "True", + "PYTHON_ENV": "C:\\Users\\Public\\pype_env", + "REMOTE_ENV_DIR": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env", + "REMOTE_ENV_ON": "0", + "SCHEMA": "avalon-core:session-1.0", + "STUDIO_SOFT": "\\\\evo2\\core\\Applications", + "TOOL_ENV": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\environments", + "USERNAME": "pype" + } diff --git a/pype/plugins/celaction/publish/validate_celaction_scene_path.py b/pype/plugins/celaction/publish/validate_celaction_scene_path.py new file mode 100644 index 0000000000..a73819b9ca --- /dev/null +++ b/pype/plugins/celaction/publish/validate_celaction_scene_path.py @@ -0,0 +1,62 @@ +import shutil +import pyblish.api +import pyblish_standalone +import os +from bait.paths import get_env_work_file + + +class RepairCelactionScenePath(pyblish.api.Action): + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + + # get version data + version = context.data('version') if context.has_data('version') else 1 + + task_id = context.data["ftrackData"]["Task"]["id"] + expected_path = get_env_work_file( + "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') + + src = context.data["currentFile"] + + if not os.path.exists(os.path.dirname(expected_path)): + os.makedirs(os.path.dirname(expected_path)) + + if os.path.exists(os.path.dirname(expected_path)): + self.log.info("existing to \"%s\"" % expected_path) + + if os.path.exists(expected_path) and ('v001' in expected_path): + os.remove(expected_path) + + shutil.copy2(src, expected_path) + + pyblish_standalone.kwargs['path'] = [expected_path] + context.data["currentFile"] = expected_path + + self.log.info("Saved to \"%s\"" % expected_path) + + +class ValidateCelactionScenePath(pyblish.api.InstancePlugin): + order = pyblish.api.ValidatorOrder + families = ['scene'] + label = 'Scene Path' + actions = [RepairCelactionScenePath] + + def process(self, instance): + + # getting current work file + current_scene_path = pyblish_standalone.kwargs['path'][0] + + version = instance.context.data( + 'version') if instance.context.has_data('version') else 1 + + task_id = instance.context.data["ftrackData"]["Task"]["id"] + expected_scene_path = get_env_work_file( + "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') + + msg = 'Scene path is not correct: Current: {}, Expected: {}'.format( + current_scene_path, expected_scene_path) + + assert expected_scene_path == current_scene_path, msg From 08bc7a533baa571e09c68485703124e64646be9c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Apr 2020 13:23:58 +0200 Subject: [PATCH 11/40] feat(celaction): prelaunch hool with Anatomy last version workfile --- pype/api.py | 2 + pype/celaction/cli.py | 13 ---- pype/hooks/celaction/prelaunch.py | 101 ++++++++++++++++++++++++++---- pype/lib.py | 34 ++++++++++ 4 files changed, 125 insertions(+), 25 deletions(-) diff --git a/pype/api.py b/pype/api.py index 2c227b5b4b..045cb260ee 100644 --- a/pype/api.py +++ b/pype/api.py @@ -25,6 +25,7 @@ from .lib import ( get_hierarchy, get_subsets, get_version_from_path, + get_last_version_from_path, modified_environ, add_tool_to_environment ) @@ -56,6 +57,7 @@ __all__ = [ "get_asset", "get_subsets", "get_version_from_path", + "get_last_version_from_path", "modified_environ", "add_tool_to_environment", diff --git a/pype/celaction/cli.py b/pype/celaction/cli.py index ddbe2ec0c0..f6d518a5a9 100644 --- a/pype/celaction/cli.py +++ b/pype/celaction/cli.py @@ -99,19 +99,6 @@ def main(): # Registers pype's Global pyblish plugins pype.install() - host_import_str = f"pype.{publish_host}" - - try: - host_module = importlib.import_module(host_import_str) - except ModuleNotFoundError: - log.error(( - f"Host \"{publish_host}\" can't be imported." - f" Import string \"{host_import_str}\" failed." - )) - return False - - # avalon.api.install(host_module) - for path in PUBLISH_PATHS: path = os.path.normpath(path) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index a31d54e920..9af3cdd740 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -2,7 +2,10 @@ import logging import os import winreg from pype.lib import PypeHook -from pypeapp import Logger +from pype.api import get_last_version_from_path +from pypeapp import Anatomy, Logger + +from avalon import io, api, lib log = logging.getLogger(__name__) @@ -14,6 +17,7 @@ class CelactionPrelaunchHook(PypeHook): path to the project by environment variable to Unreal launcher shell script. """ + workfile_ext = "scn" def __init__(self, logger=None): if not logger: @@ -25,21 +29,33 @@ class CelactionPrelaunchHook(PypeHook): def execute(self, *args, env: dict = None) -> bool: if not env: - env = os.environ - project = env["AVALON_PROJECT"] - asset = env["AVALON_ASSET"] - task = env["AVALON_TASK"] - app = "celaction_publish" - workdir = env["AVALON_WORKDIR"] - project_name = f"{asset}_{task}" - version = "v001" + self.env = os.environ + else: + self.env = env - self.log.info(f"{self.signature}") + self._S = api.Session + project = self._S["AVALON_PROJECT"] = self.env["AVALON_PROJECT"] + asset = self._S["AVALON_ASSET"] = self.env["AVALON_ASSET"] + task = self._S["AVALON_TASK"] = self.env["AVALON_TASK"] + workdir = self._S["AVALON_WORKDIR"] = self.env["AVALON_WORKDIR"] + + anatomy_filled = self.get_anatomy_filled() + + app = "celaction_publish" + workfile = anatomy_filled["work"]["file"] + version = anatomy_filled["version"] os.makedirs(workdir, exist_ok=True) self.log.info(f"Work dir is: `{workdir}`") - project_file = os.path.join(workdir, f"{project_name}_{version}.scn") + # get last version if any + workfile_last = get_last_version_from_path( + workdir, workfile.split(version)) + + if workfile_last: + workfile = workfile_last + + project_file = os.path.join(workdir, workfile) env["PYPE_CELACTION_PROJECT_FILE"] = project_file self.log.info(f"Workfile is: `{project_file}`") @@ -73,7 +89,7 @@ class CelactionPrelaunchHook(PypeHook): "--resolutionWidth *X*", "--resolutionHeight *Y*", "--programDir \"'*PROGPATH*'\"" - ] + ] winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, " ".join(parameters)) @@ -105,3 +121,64 @@ class CelactionPrelaunchHook(PypeHook): winreg.SetValueEx(hKey, "Valid", 0, winreg.REG_DWORD, 1) return True + + def get_anatomy_filled(self): + root_path = api.registered_root() + project_name = self._S["AVALON_PROJECT"] + asset_name = self._S["AVALON_ASSET"] + + io.install() + project_entity = io.find_one({ + "type": "project", + "name": project_name + }) + assert project_entity, ( + "Project '{0}' was not found." + ).format(project_name) + log.debug("Collected Project \"{}\"".format(project_entity)) + + asset_entity = io.find_one({ + "type": "asset", + "name": asset_name, + "parent": project_entity["_id"] + }) + assert asset_entity, ( + "No asset found by the name '{0}' in project '{1}'" + ).format(asset_name, project_name) + + project_name = project_entity["name"] + + log.info( + "Anatomy object collected for project \"{}\".".format(project_name) + ) + + hierarchy_items = asset_entity["data"]["parents"] + hierarchy = "" + if hierarchy_items: + hierarchy = os.path.join(*hierarchy_items) + + template_data = { + "root": root_path, + "project": { + "name": project_name, + "code": project_entity["data"].get("code") + }, + "asset": asset_entity["name"], + "hierarchy": hierarchy.replace("\\", "/"), + "task": self._S["AVALON_TASK"], + "ext": self.workfile_ext, + "version": 1, + "username": os.getenv("PYPE_USERNAME", "").strip() + } + + avalon_app_name = os.environ.get("AVALON_APP_NAME") + if avalon_app_name: + application_def = lib.get_application(avalon_app_name) + app_dir = application_def.get("application_dir") + if app_dir: + template_data["app"] = app_dir + + anatomy = Anatomy(project_name) + anatomy_filled = anatomy.format_all(template_data).get_solved() + + return anatomy_filled diff --git a/pype/lib.py b/pype/lib.py index d3ccbc8589..2bd18dacff 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -469,6 +469,40 @@ def get_version_from_path(file): ) +def get_last_version_from_path(path_dir, filter=None): + """ + Finds last version of given directory content + + Args: + path_dir (string): directory path + filter (list): list of strings used as file name filter + + Returns: + string: file name with last version + + """ + assert os.path.isdir(path_dir), "`path_dir` argument needs to be directory" + + filtred_files = list() + + # form regex for filtering + patern = r".*" + + if filter: + patern = patern.join(filter) + + for f in os.listdir(path_dir): + if not re.findall(patern, f): + continue + filtred_files.append(f) + + if filtred_files: + sorted(filtred_files) + return filtred_files[-1] + else: + return None + + def get_avalon_database(): if io._database is None: set_io_database() From bbb4240598fdd590ae0c1d2f241708c629b55d32 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Apr 2020 15:30:37 +0200 Subject: [PATCH 12/40] feat(celaction): wip support deadline submition --- .../celaction/publish/collect_render_path.py | 60 ++++ ...e.py => extract_celaction_deadline_old.py} | 218 ++++++------- .../publish/submit_celaction_deadline.py | 287 ++++++++++++++++++ 3 files changed, 456 insertions(+), 109 deletions(-) create mode 100644 pype/plugins/celaction/publish/collect_render_path.py rename pype/plugins/celaction/publish/{extract_celaction_deadline.py => extract_celaction_deadline_old.py} (95%) create mode 100644 pype/plugins/celaction/publish/submit_celaction_deadline.py diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py new file mode 100644 index 0000000000..72adf57e86 --- /dev/null +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -0,0 +1,60 @@ +""" +Requires: + context -> anatomy + context -> anatomyData + +Provides: + instance -> publishDir + instance -> resourcesDir +""" + +import os +import copy + +import pyblish.api +from avalon import api + + +class CollectRenderPath(pyblish.api.InstancePlugin): + """Generate file and directory path where rendered images will be""" + + label = "Collect Render Path" + order = pyblish.api.CollectorOrder + 0.495 + + def process(self, instance): + anatomy = instance.context.data["anatomy"] + + template_data = copy.deepcopy(instance.data["anatomyData"]) + + # This is for cases of Deprecated anatomy without `folder` + # TODO remove when all clients have solved this issue + template_data.update({ + "frame": "FRAME_TEMP", + "representation": "png" + }) + + anatomy_filled = anatomy.format(template_data) + + if "folder" in anatomy.templates["render"]: + render_folder = anatomy_filled["render"]["folder"] + render_file = anatomy_filled["render"]["file"] + else: + # solve deprecated situation when `folder` key is not underneath + # `publish` anatomy + project_name = api.Session["AVALON_PROJECT"] + self.log.warning(( + "Deprecation warning: Anatomy does not have set `folder`" + " key underneath `publish` (in global of for project `{}`)." + ).format(project_name)) + + file_path = anatomy_filled["render"]["path"] + # Directory + render_folder = os.path.dirname(file_path) + render_file = os.path.basename(file_path) + + render_folder = os.path.normpath(render_folder) + render_path = os.path.join(render_folder, render_file) + + instance.data["outputRenderPath"] = render_path + + self.log.debug("outputRenderPath: \"{}\"".format(render_path)) diff --git a/pype/plugins/celaction/publish/extract_celaction_deadline.py b/pype/plugins/celaction/publish/extract_celaction_deadline_old.py similarity index 95% rename from pype/plugins/celaction/publish/extract_celaction_deadline.py rename to pype/plugins/celaction/publish/extract_celaction_deadline_old.py index 3deb612fd5..322bb468f9 100644 --- a/pype/plugins/celaction/publish/extract_celaction_deadline.py +++ b/pype/plugins/celaction/publish/extract_celaction_deadline_old.py @@ -1,109 +1,109 @@ -import os - -import pyblish.api -import pyblish_standalone -import clique -import requests -from bait.deadline import get_render_settings, get_deadline_data, format_frames -from bait.paths import get_output_path - - -class ExtractCelactionDeadline(pyblish.api.InstancePlugin): - - label = 'Deadline' - families = ['render'] - order = pyblish.api.ExtractorOrder - - def process(self, instance): - - render_settings = get_render_settings("celaction") - - existing_data = instance.data.get( - "deadlineData", {"job": {}, "plugin": {}} - ) - - task_id = instance.context.data["ftrackData"]["Task"]["id"] - - data = get_deadline_data(render_settings, existing_data) - - filename = os.path.basename(instance.context.data["currentFile"]) - filename_no_ext, ext = os.path.splitext(filename) - - data["job"]["Name"] = filename_no_ext + " - " + instance.data["name"] - data["job"]['Frames'] = format_frames( - instance.data('start'), instance.data('end')) - - # get version data - version = instance.context.data( - 'version') if instance.context.has_data('version') else 1 - - output_path = get_output_path( - task_id, instance.data["name"], version, "png") - output_path = output_path.replace("/", "\\") - - data['job']['Plugin'] = 'CelAction' - data['job']["BatchName"] = filename - data['job']["UserName"] = os.environ['USERNAME'] - data["job"]['OutputFilename0'] = output_path.replace('%04d', '####') - - scene_path = pyblish_standalone.kwargs['path'][0] - scene_path = scene_path.replace("/", "\\") - _, ext = os.path.splitext(scene_path) - - # plugin data - self.log.info(scene_path) - - args = '{}'.format(scene_path) - args += ' -a' - args += ' -8' - args += ' -s ' - args += ' -e ' - args += ' -d {}'.format(os.path.dirname(output_path)) - args += ' -x {}'.format(instance.data('x')) - args += ' -y {}'.format(instance.data('y')) - args += ' -r {}'.format(output_path.replace('.%04d', '')) - args += ' -= AbsoluteFrameNumber=on -= PadDigits=4' - args += ' -= ClearAttachment=on' - - data["plugin"]['StartupDirectory'] = '' - data["plugin"]['Arguments'] = args - - self.log.info(data) - - head = output_path.replace('%04d', '') - tail = ".png" - collection = clique.Collection(head=head, padding=4, tail=tail) - - frame_start = int(instance.data['start']) - frame_end = int(instance.data['end']) - - for frame_no in range(frame_start, frame_end): - collection.add(head + str(frame_no).zfill(4) + tail) - - # instance.data["collection"] = collection - - # adding to instance - instance.set_data('deadlineData2', value=data) - # instance.set_data('deadlineSubmissionJob', value=data) - - payload = { - "JobInfo": data["job"], - "PluginInfo": data["plugin"], - "AuxFiles": [] - } - - url = "{}/api/jobs".format('http://192.168.146.8:8082') - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - - # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname( - data["job"]['OutputFilename0']) - instance.data["deadlineSubmissionJob"] = response.json() - - instance.context.data['ftrackStatus'] = "Render" - - # creating output path - if not os.path.exists(os.path.dirname(output_path)): - os.makedirs(os.path.dirname(output_path)) +import os + +import pyblish.api +import pyblish_standalone +import clique +import requests +from bait.deadline import get_render_settings, get_deadline_data, format_frames +from bait.paths import get_output_path + + +class ExtractCelactionDeadline(pyblish.api. InstancePlugin): + + label = 'Deadline' + families = ['render'] + order = pyblish.api.ExtractorOrder + + def process(self, instance): + + render_settings = get_render_settings("celaction") + + existing_data = instance.data.get( + "deadlineData", {"job": {}, "plugin": {}} + ) + + task_id = instance.context.data["ftrackData"]["Task"]["id"] + + data = get_deadline_data(render_settings, existing_data) + + filename = os.path.basename(instance.context.data["currentFile"]) + filename_no_ext, ext = os.path.splitext(filename) + + data["job"]["Name"] = filename_no_ext + " - " + instance.data["name"] + data["job"]['Frames'] = format_frames( + instance.data('start'), instance.data('end')) + + # get version data + version = instance.context.data( + 'version') if instance.context.has_data('version') else 1 + + output_path = get_output_path( + task_id, instance.data["name"], version, "png") + output_path = output_path.replace("/", "\\") + + data['job']['Plugin'] = 'CelAction' + data['job']["BatchName"] = filename + data['job']["UserName"] = os.environ['USERNAME'] + data["job"]['OutputFilename0'] = output_path.replace('%04d', '####') + + scene_path = pyblish_standalone.kwargs['path'][0] + scene_path = scene_path.replace("/", "\\") + _, ext = os.path.splitext(scene_path) + + # plugin data + self.log.info(scene_path) + + args = '{}'.format(scene_path) + args += ' -a' + args += ' -8' + args += ' -s ' + args += ' -e ' + args += ' -d {}'.format(os.path.dirname(output_path)) + args += ' -x {}'.format(instance.data('x')) + args += ' -y {}'.format(instance.data('y')) + args += ' -r {}'.format(output_path.replace('.%04d', '')) + args += ' -= AbsoluteFrameNumber=on -= PadDigits=4' + args += ' -= ClearAttachment=on' + + data["plugin"]['StartupDirectory'] = '' + data["plugin"]['Arguments'] = args + + self.log.info(data) + + head = output_path.replace('%04d', '') + tail = ".png" + collection = clique.Collection(head=head, padding=4, tail=tail) + + frame_start = int(instance.data['start']) + frame_end = int(instance.data['end']) + + for frame_no in range(frame_start, frame_end): + collection.add(head + str(frame_no).zfill(4) + tail) + + # instance.data["collection"] = collection + + # adding to instance + instance.set_data('deadlineData2', value=data) + # instance.set_data('deadlineSubmissionJob', value=data) + + payload = { + "JobInfo": data["job"], + "PluginInfo": data["plugin"], + "AuxFiles": [] + } + + url = "{}/api/jobs".format('http://192.168.146.8:8082') + response = requests.post(url, json=payload) + if not response.ok: + raise Exception(response.text) + + # Store output dir for unified publisher (filesequence) + instance.data["outputDir"] = os.path.dirname( + data["job"]['OutputFilename0']) + instance.data["deadlineSubmissionJob"] = response.json() + + instance.context.data['ftrackStatus'] = "Render" + + # creating output path + if not os.path.exists(os.path.dirname(output_path)): + os.makedirs(os.path.dirname(output_path)) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py new file mode 100644 index 0000000000..3ea9b929e3 --- /dev/null +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -0,0 +1,287 @@ +import os +import json +import getpass + +from avalon import api +from avalon.vendor import requests +import re +import pyblish.api + + +class ExtractCelactionDeadline(pyblish.api.InstancePlugin): + """Submit CelAction2D scene to Deadline + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable DEADLINE_REST_URL + + """ + + label = "Submit CelAction to Deadline" + order = pyblish.api.IntegratorOrder + 0.1 + hosts = ["celaction"] + families = ["render"] + + deadline_department = "" + deadline_priority = 50 + deadline_pool = "" + deadline_pool_secondary = "" + deadline_group = "" + deadline_chunk_size = 1 + + def process(self, instance): + families = instance.data["families"] + + context = instance.context + + DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL", + "http://localhost:8082") + assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" + + self.deadline_url = "{}/api/jobs".format(DEADLINE_REST_URL) + self._comment = context.data.get("comment", "") + self._ver = re.search(r"\d+\.\d+", context.data.get("hostVersion")) + self._deadline_user = context.data.get( + "deadlineUser", getpass.getuser()) + self._frame_start = int(instance.data["frameStartHandle"]) + self._frame_end = int(instance.data["frameEndHandle"]) + + # get output path + render_path = instance.data['path'] + script_path = context.data["currentFile"] + + response = self.payload_submit(instance, + script_path, + render_path + ) + # Store output dir for unified publisher (filesequence) + instance.data["deadlineSubmissionJob"] = response.json() + + instance.data["outputDir"] = os.path.dirname( + render_path).replace("\\", "/") + + instance.data["publishJobState"] = "Suspended" + + # adding 2d render specific family for version identification in Loader + instance.data["families"] = families.insert(0, "render2d") + + def payload_submit(self, + instance, + script_path, + render_path, + responce_data=None + ): + render_dir = os.path.normpath(os.path.dirname(render_path)) + script_name = os.path.basename(script_path) + jobname = "%s - %s" % (script_name, instance.name) + + output_filename_0 = self.preview_fname(render_path) + + if not responce_data: + responce_data = {} + + try: + # Ensure render folder exists + os.makedirs(render_dir) + except OSError: + pass + + # define chunk and priority + chunk_size = instance.data.get("deadlineChunkSize") + if chunk_size == 0: + chunk_size = self.deadline_chunk_size + + payload = { + "JobInfo": { + # Top-level group name + "BatchName": script_name, + + # Job name, as seen in Monitor + "Name": jobname, + + # Arbitrary username, for visualisation in Monitor + "UserName": self._deadline_user, + + "Department": self.deadline_department, + "Priority": self.deadline_priority, + "ChunkSize": chunk_size, + + "Group": self.deadline_group, + "Pool": self.deadline_pool, + "SecondaryPool": self.deadline_pool_secondary, + + "Plugin": "CelAction", + "Frames": "{start}-{end}".format( + start=self._frame_start, + end=self._frame_end + ), + "Comment": self._comment, + + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor + "OutputFilename0": output_filename_0.replace("\\", "/") + + }, + "PluginInfo": { + # Input + "SceneFile": script_path, + + # Output directory and filename + "OutputFilePath": render_dir.replace("\\", "/"), + + # Mandatory for Deadline + "Version": self._ver.group(), + + # Resolve relative references + "ProjectPath": script_path, + "AWSAssetFile0": render_path, + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + if responce_data.get("_id"): + payload["JobInfo"].update({ + "JobType": "Normal", + "BatchName": responce_data["Props"]["Batch"], + "JobDependency0": responce_data["_id"], + "ChunkSize": 99999999 + }) + + # Include critical environment variables with submission + keys = [ + "PYTHONPATH", + "PATH", + "AVALON_SCHEMA", + "PYBLISHPLUGINPATH", + "TOOL_ENV" + ] + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **api.Session) + + for path in os.environ: + if path.lower().startswith('pype_'): + environment[path] = os.environ[path] + + environment["PATH"] = os.environ["PATH"] + clean_environment = {} + for key in environment: + clean_path = "" + self.log.debug("key: {}".format(key)) + to_process = environment[key] + if key == "PYPE_STUDIO_CORE_MOUNT": + clean_path = environment[key] + elif "://" in environment[key]: + clean_path = environment[key] + elif os.pathsep not in to_process: + try: + path = environment[key] + path.decode('UTF-8', 'strict') + clean_path = os.path.normpath(path) + except UnicodeDecodeError: + print('path contains non UTF characters') + else: + for path in environment[key].split(os.pathsep): + try: + path.decode('UTF-8', 'strict') + clean_path += os.path.normpath(path) + os.pathsep + except UnicodeDecodeError: + print('path contains non UTF characters') + + if key == "PYTHONPATH": + clean_path = clean_path.replace('python2', 'python3') + + clean_path = clean_path.replace( + os.path.normpath( + environment['PYPE_STUDIO_CORE_MOUNT']), # noqa + os.path.normpath( + environment['PYPE_STUDIO_CORE_PATH'])) # noqa + clean_environment[key] = clean_path + + environment = clean_environment + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + plugin = payload["JobInfo"]["Plugin"] + self.log.info("using render plugin : {}".format(plugin)) + + self.log.info("Submitting..") + self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + + # adding expectied files to instance.data + self.expected_files(instance, render_path) + self.log.debug("__ expectedFiles: `{}`".format( + instance.data["expectedFiles"])) + response = requests.post(self.deadline_url, json=payload) + + if not response.ok: + raise Exception(response.text) + + return response + + def preflight_check(self, instance): + """Ensure the startFrame, endFrame and byFrameStep are integers""" + + for key in ("frameStart", "frameEnd"): + value = instance.data[key] + + if int(value) == value: + continue + + self.log.warning( + "%f=%d was rounded off to nearest integer" + % (value, int(value)) + ) + + def preview_fname(self, path): + """Return output file path with #### for padding. + + Deadline requires the path to be formatted with # in place of numbers. + For example `/path/to/render.####.png` + + Args: + path (str): path to rendered images + + Returns: + str + + """ + self.log.debug("_ path: `{}`".format(path)) + if "%" in path: + search_results = re.search(r"(%0)(\d)(d.)", path).groups() + self.log.debug("_ search_results: `{}`".format(search_results)) + return int(search_results[1]) + if "#" in path: + self.log.debug("_ path: `{}`".format(path)) + return path + else: + return path + + def expected_files(self, + instance, + path): + """ Create expected files in instance data + """ + if not instance.data.get("expectedFiles"): + instance.data["expectedFiles"] = list() + + dir = os.path.dirname(path) + file = os.path.basename(path) + + if "#" in file: + pparts = file.split("#") + padding = "%0{}d".format(len(pparts) - 1) + file = pparts[0] + padding + pparts[-1] + + if "%" not in file: + instance.data["expectedFiles"].append(path) + return + + for i in range(self._frame_start, (self._frame_end + 1)): + instance.data["expectedFiles"].append( + os.path.join(dir, (file % i)).replace("\\", "/")) From 1faec702ec21c77bcbce9b420dea461bda7a08cd Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Apr 2020 15:50:10 +0200 Subject: [PATCH 13/40] clean(celaction): removing old plugins --- .../append_celaction_ftrack_asset_name.py | 38 +- .../append_celaction_ftrack_data.py | 92 +-- .../extract_celaction_deadline_old.py | 0 .../submit_publish_job.py | 566 +++++++++--------- .../validate_celaction_scene_path.py | 124 ++-- 5 files changed, 410 insertions(+), 410 deletions(-) rename pype/plugins/celaction/{publish => _unused_publish}/append_celaction_ftrack_asset_name.py (96%) rename pype/plugins/celaction/{publish => _unused_publish}/append_celaction_ftrack_data.py (96%) rename pype/plugins/celaction/{publish => _unused_publish}/extract_celaction_deadline_old.py (100%) rename pype/plugins/celaction/{publish => _unused_publish}/submit_publish_job.py (97%) rename pype/plugins/celaction/{publish => _unused_publish}/validate_celaction_scene_path.py (96%) diff --git a/pype/plugins/celaction/publish/append_celaction_ftrack_asset_name.py b/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_asset_name.py similarity index 96% rename from pype/plugins/celaction/publish/append_celaction_ftrack_asset_name.py rename to pype/plugins/celaction/_unused_publish/append_celaction_ftrack_asset_name.py index 734bdffe39..175a19edd3 100644 --- a/pype/plugins/celaction/publish/append_celaction_ftrack_asset_name.py +++ b/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_asset_name.py @@ -1,19 +1,19 @@ -import pyblish.api - - -class AppendCelactionFtrackAssetName(pyblish.api.InstancePlugin): - """ Appending "ftrackAssetName" """ - - label = "Ftrack Asset Name" - order = pyblish.api.CollectorOrder + 0.1 - - def process(self, instance): - - # skipping if not launched from ftrack - if "ftrackData" not in instance.context.data: - return - - ftrack_data = instance.context.data["ftrackData"] - - asset_name = ftrack_data["Task"]["name"] - instance.data["ftrackAssetName"] = asset_name +import pyblish.api + + +class AppendCelactionFtrackAssetName(pyblish.api.InstancePlugin): + """ Appending "ftrackAssetName" """ + + label = "Ftrack Asset Name" + order = pyblish.api.CollectorOrder + 0.1 + + def process(self, instance): + + # skipping if not launched from ftrack + if "ftrackData" not in instance.context.data: + return + + ftrack_data = instance.context.data["ftrackData"] + + asset_name = ftrack_data["Task"]["name"] + instance.data["ftrackAssetName"] = asset_name diff --git a/pype/plugins/celaction/publish/append_celaction_ftrack_data.py b/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_data.py similarity index 96% rename from pype/plugins/celaction/publish/append_celaction_ftrack_data.py rename to pype/plugins/celaction/_unused_publish/append_celaction_ftrack_data.py index fab3ae8b1a..552f4ffb1a 100644 --- a/pype/plugins/celaction/publish/append_celaction_ftrack_data.py +++ b/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_data.py @@ -1,46 +1,46 @@ -import pyblish.api -from bait.ftrack.query_runner import QueryRunner - - -class AppendCelactionFtrackAudio(pyblish.api.ContextPlugin): - - label = "Ftrack Audio" - order = pyblish.api.ExtractorOrder - - def process(self, context): - - if context.data.get("audio", ''): - self.log.info('Audio data are already collected') - self.log.info('Audio: {}'.format(context.data.get("audio", ''))) - return - - runner = QueryRunner(context.data['ftrackSession']) - - audio_file = runner.get_audio_file_for_shot( - context.data['ftrackData']["Shot"]["id"]) - - if audio_file: - context.data["audio"] = { - 'filename': audio_file, - 'enabled': True - } - else: - self.log.warning("Couldn't find any audio file on Ftrack.") - - -class AppendCelactionFtrackData(pyblish.api.InstancePlugin): - """ Appending ftrack component and asset type data """ - - families = ["img.*", "mov.*"] - # offset to piggy back from default collectors - order = pyblish.api.CollectorOrder + 0.1 - - def process(self, instance): - - # ftrack data - if not instance.context.has_data("ftrackData"): - return - - instance.data["ftrackComponents"] = {} - asset_type = instance.data["family"].split(".")[0] - instance.data["ftrackAssetType"] = asset_type +import pyblish.api +from bait.ftrack.query_runner import QueryRunner + + +class AppendCelactionFtrackAudio(pyblish.api.ContextPlugin): + + label = "Ftrack Audio" + order = pyblish.api.ExtractorOrder + + def process(self, context): + + if context.data.get("audio", ''): + self.log.info('Audio data are already collected') + self.log.info('Audio: {}'.format(context.data.get("audio", ''))) + return + + runner = QueryRunner(context.data['ftrackSession']) + + audio_file = runner.get_audio_file_for_shot( + context.data['ftrackData']["Shot"]["id"]) + + if audio_file: + context.data["audio"] = { + 'filename': audio_file, + 'enabled': True + } + else: + self.log.warning("Couldn't find any audio file on Ftrack.") + + +class AppendCelactionFtrackData(pyblish.api.InstancePlugin): + """ Appending ftrack component and asset type data """ + + families = ["img.*", "mov.*"] + # offset to piggy back from default collectors + order = pyblish.api.CollectorOrder + 0.1 + + def process(self, instance): + + # ftrack data + if not instance.context.has_data("ftrackData"): + return + + instance.data["ftrackComponents"] = {} + asset_type = instance.data["family"].split(".")[0] + instance.data["ftrackAssetType"] = asset_type diff --git a/pype/plugins/celaction/publish/extract_celaction_deadline_old.py b/pype/plugins/celaction/_unused_publish/extract_celaction_deadline_old.py similarity index 100% rename from pype/plugins/celaction/publish/extract_celaction_deadline_old.py rename to pype/plugins/celaction/_unused_publish/extract_celaction_deadline_old.py diff --git a/pype/plugins/celaction/publish/submit_publish_job.py b/pype/plugins/celaction/_unused_publish/submit_publish_job.py similarity index 97% rename from pype/plugins/celaction/publish/submit_publish_job.py rename to pype/plugins/celaction/_unused_publish/submit_publish_job.py index 8d73dc245f..2767378b4a 100644 --- a/pype/plugins/celaction/publish/submit_publish_job.py +++ b/pype/plugins/celaction/_unused_publish/submit_publish_job.py @@ -1,283 +1,283 @@ -import os -import json -import pprint -import re -import requests -import pyblish.api - - -class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): - """Submit image sequence publish jobs to Deadline. - - These jobs are dependent on a deadline job submission prior to this - plug-in. - - Renders are submitted to a Deadline Web Service as - supplied via the environment variable DEADLINE_REST_URL - - Options in instance.data: - - deadlineSubmission (dict, Required): The returned .json - data from the job submission to deadline. - - - outputDir (str, Required): The output directory where the metadata - file should be generated. It's assumed that this will also be - final folder containing the output files. - - - ext (str, Optional): The extension (including `.`) that is required - in the output filename to be picked up for image sequence - publishing. - - - publishJobState (str, Optional): "Active" or "Suspended" - This defaults to "Suspended" - - This requires a "startFrame" and "endFrame" to be present in instance.data - or in context.data. - - """ - - label = "Submit image sequence jobs to Deadline" - order = pyblish.api.IntegratorOrder + 0.2 - - hosts = ["celaction"] - - families = [ - "render", - "deadline" - ] - - def process(self, instance): - - # DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL", - # "http://localhost:8082") - # assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" - - # Get a submission job - - job = instance.data.get("deadlineSubmissionJob") - if not job: - raise RuntimeError("Can't continue without valid deadline " - "submission prior to this plug-in.") - ################ - ft_data = instance.context.data["ftrackData"] - project = ft_data['Project']['name'] - project_code = ft_data['Project']['code'] - projects_path = os.path.dirname(ft_data['Project']['root']) - - data = instance.data.copy() - asset = instance.context.data["ftrackData"]['Shot']['name'] - subset = 'render' + \ - instance.context.data["ftrackData"]['Task']['name'].capitalize() - - state = data.get("publishJobState", "Suspended") - # job_name = "{batch} - {subset} [publish image sequence]".format( - # batch=job["Props"]["Name"], - # subset=subset - # ) - job_name = "{asset} [publish image sequence]".format( - asset=asset - ) - - # Get start/end frame from instance, if not available get from context - context = instance.context - - start = int(instance.data['start']) - end = int(instance.data['end']) - - try: - source = data['source'] - except KeyError: - source = context.data["currentFile"] - - # Write metadata for publish job - render_job = data.pop("deadlineSubmissionJob") - metadata = { - "asset": asset, - "regex": r"^.*\.png", - "subset": subset, - "startFrame": start, - "endFrame": end, - "fps": context.data.get("fps", None), - "families": ["render"], - "source": source, - "user": context.data["user"], - "version": context.data.get('version'), - "audio": context.data["audio"]['filename'], - # Optional metadata (for debugging) - "metadata": { - "instance": data, - "job": job, - "session": fake_avalon_session(project, projects_path) - } - } - - # Ensure output dir exists - output_dir = instance.data["outputDir"] - - if not os.path.isdir(output_dir): - os.makedirs(output_dir) - - for k, v in metadata.items(): - self.log.info(k) - self.log.info(v) - - metadata_filename = "{}_metadata.json".format(subset) - metadata_path = os.path.join(output_dir, metadata_filename) - with open(metadata_path, "w") as f: - json.dump(metadata, f, indent=4, sort_keys=True) - - # Generate the payload for Deadline submission - payload = { - "JobInfo": { - "Plugin": "Python", - "BatchName": job["Props"]["Batch"], - "Name": job_name, - "JobType": "Normal", - "Group": "celaction", - "JobDependency0": job["_id"], - "UserName": os.environ['USERNAME'], - "Comment": instance.context.data.get("comment", ""), - "InitialStatus": "Active" - }, - "PluginInfo": { - "Version": "3.6", - "ScriptFile": r"\\pype\Core\dev\pype-setup\repos\pype-config\pype\scripts\publish_filesequence.py", - "Arguments": '--path "{}"'.format(metadata_path), - "SingleFrameOnly": "True" - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - # Transfer the environment from the original job to this dependent - # job so they use the same environment - environment = fake_env() - environment["AVALON_ASSET"] = asset - environment["AVALON_TASK"] = instance.context.data["ftrackData"]['Task']['name'] - environment["AVALON_PROJECT"] = project - environment["AVALON_PROJECTS"] = projects_path - environment["PYPE_STUDIO_PROJECTS_PUBLISH"] = ft_data['Project']['root'] - environment["PYPE_STUDIO_PROJECTS_RENDER"] = ft_data['Project']['root'] - environment["PYPE_STUDIO_PROJECTS_RESOURCES"] = ft_data['Project']['root'] - environment["PYPE_STUDIO_PROJECTS_WORK"] = ft_data['Project']['root'] - - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - - # Avoid copied pools and remove secondary pool - payload["JobInfo"]["Pool"] = "animation_2d" - payload["JobInfo"].pop("SecondaryPool", None) - - self.log.info("Submitting..") - # self.log.info(json.dumps(payload, indent=4, sort_keys=True)) - - ################ - ###################### - fake_instance = instance.context.create_instance( - name=(str(instance) + "1")) - - for k, v in data.items(): - self.log.info(k) - fake_instance.data[k] = v - - # fake_instance.data['deadlineData'] = payload - # 'http://192.168.146.8:8082' - url = "{}/api/jobs".format('http://192.168.146.8:8082') - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - ###################### - ####################### - - -def fake_avalon_session(project=None, projects_path=None): - return { - "AVALON_APP": "premiere", - "AVALON_APP_VERSION": "2019", - "AVALON_ASSET": "editorial", - "AVALON_CONFIG": "pype", - "AVALON_CONTAINER_ID": "avalon.container", - "AVALON_DB": "Pype", - "AVALON_DEADLINE": "http://192.168.146.8:8082", - "AVALON_DEBUG": "1", - "AVALON_EARLY_ADOPTER": "1", - "AVALON_INSTANCE_ID": "avalon.instance", - "AVALON_LABEL": "Avalon", - "AVALON_LOCATION": "http://127.0.0.1", - "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", - "AVALON_PASSWORD": "secret", - "AVALON_PROJECT": project or "LBB2_dev", - "AVALON_PROJECTS": projects_path or "L:/PYPE_test", - "AVALON_SILO": "editorial", - "AVALON_TASK": "conform", - "AVALON_TIMEOUT": "1000", - "AVALON_USERNAME": "avalon", - "AVALON_WORKDIR": "L:/PYPE_test/episodes/editorial/work/conform", - "schema": "avalon-core:session-1.0" - } - - -def fake_env(): - return { - "AVALON_CONFIG": "pype", - "AVALON_CONTAINER_ID": "avalon.container", - "AVALON_CORE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core", - "AVALON_DB": "Pype", - "AVALON_DB_DATA": "\\\\pype\\Core\\dev\\mongo_db_data", - "AVALON_DEADLINE": "http://192.168.146.8:8082", - "AVALON_DEBUG": "1", - "AVALON_EARLY_ADOPTER": "1", - "AVALON_ENV_NAME": "pype_env", - "AVALON_HIERARCHY": "", - "AVALON_INSTANCE_ID": "avalon.instance", - "AVALON_LABEL": "Avalon", - "AVALON_LAUNCHER": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher", - "AVALON_LOCATION": "http://127.0.0.1", - "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", - "AVALON_MONGO_PORT": "27072", - "AVALON_PASSWORD": "secret", - "AVALON_SCHEMA": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\schema", - "AVALON_SILO": "", - "AVALON_TIMEOUT": "1000", - "AVALON_USERNAME": "avalon", - "AVALON_WORKDIR": "default", - "DEADLINE_PATH": "C:\\Program Files\\Thinkbox\\Deadline10\\bin", - "DEADLINE_REST_URL": "http://192.168.146.8:8082", - "FTRACK_API_KEY": "NGI0ZGU3ZjMtNzNiZC00NGVlLWEwY2EtMzA1OWJlZGM0MjAyOjozZWZmMThjZi04MjkwLTQxMzQtODUwMC03NTZhMGJiZTM2MTA", - "FTRACK_API_USER": "license@clothcatanimation.com", - "FTRACK_SERVER": "https://clothcat2.ftrackapp.com", - "MONGO_DB_ENTRYDB": "Pype", - "MONGO_DB_PASS": "X34vkuwL4wbK9A7X", - "MONGO_DB_USER": "PypeAdmin", - "PATH": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Scripts;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library;;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin\\windows;\\\\pype\\Core\\dev\\pype-setup\\app;\\\\pype\\core\\software\\ffmpeg\\bin;\\\\pype\\Core\\dev\\Applications\\djv\\bin", - "PYBLISHPLUGINPATH": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\plugins\\ftrack\\publish;", - "PYBLISH_BASE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base", - "PYBLISH_HOSTS": "shell", - "PYBLISH_LITE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite", - "PYBLISH_QML": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml", - "PYPE_APP_ROOT": "\\\\pype\\Core\\dev\\pype-setup\\app", - "PYPE_DEBUG": "3", - "PYPE_DEBUG_STDOUT": "0", - "PYPE_SETUP_ROOT": "\\\\pype\\Core\\dev\\pype-setup", - "PYPE_STUDIO_CODE": "CC", - "PYPE_STUDIO_CONFIG": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config", - "PYPE_STUDIO_CORE": "\\\\pype\\Core\\dev", - "PYPE_STUDIO_CORE_MOUNT": "\\\\pype\\Core\\dev", - "PYPE_STUDIO_NAME": "Cloth Cat", - "PYPE_STUDIO_SOFTWARE": "\\\\pype\\Core\\dev\\Applications", - "PYPE_STUDIO_TEMPLATES": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates", - "PYPE_STUDIO_TOOLS": "\\\\pype\\Core\\dev\\production\\tools", - "PYTHONPATH": "\\\\pype\\Core\\dev\\pype-setup;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server", - "PYTHONVERBOSE": "True", - "PYTHON_ENV": "C:\\Users\\Public\\pype_env", - "REMOTE_ENV_DIR": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env", - "REMOTE_ENV_ON": "0", - "SCHEMA": "avalon-core:session-1.0", - "STUDIO_SOFT": "\\\\evo2\\core\\Applications", - "TOOL_ENV": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\environments", - "USERNAME": "pype" - } +import os +import json +import pprint +import re +import requests +import pyblish.api + + +class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): + """Submit image sequence publish jobs to Deadline. + + These jobs are dependent on a deadline job submission prior to this + plug-in. + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable DEADLINE_REST_URL + + Options in instance.data: + - deadlineSubmission (dict, Required): The returned .json + data from the job submission to deadline. + + - outputDir (str, Required): The output directory where the metadata + file should be generated. It's assumed that this will also be + final folder containing the output files. + + - ext (str, Optional): The extension (including `.`) that is required + in the output filename to be picked up for image sequence + publishing. + + - publishJobState (str, Optional): "Active" or "Suspended" + This defaults to "Suspended" + + This requires a "startFrame" and "endFrame" to be present in instance.data + or in context.data. + + """ + + label = "Submit image sequence jobs to Deadline" + order = pyblish.api.IntegratorOrder + 0.2 + + hosts = ["celaction"] + + families = [ + "render", + "deadline" + ] + + def process(self, instance): + + # DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL", + # "http://localhost:8082") + # assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" + + # Get a submission job + + job = instance.data.get("deadlineSubmissionJob") + if not job: + raise RuntimeError("Can't continue without valid deadline " + "submission prior to this plug-in.") + ################ + ft_data = instance.context.data["ftrackData"] + project = ft_data['Project']['name'] + project_code = ft_data['Project']['code'] + projects_path = os.path.dirname(ft_data['Project']['root']) + + data = instance.data.copy() + asset = instance.context.data["ftrackData"]['Shot']['name'] + subset = 'render' + \ + instance.context.data["ftrackData"]['Task']['name'].capitalize() + + state = data.get("publishJobState", "Suspended") + # job_name = "{batch} - {subset} [publish image sequence]".format( + # batch=job["Props"]["Name"], + # subset=subset + # ) + job_name = "{asset} [publish image sequence]".format( + asset=asset + ) + + # Get start/end frame from instance, if not available get from context + context = instance.context + + start = int(instance.data['start']) + end = int(instance.data['end']) + + try: + source = data['source'] + except KeyError: + source = context.data["currentFile"] + + # Write metadata for publish job + render_job = data.pop("deadlineSubmissionJob") + metadata = { + "asset": asset, + "regex": r"^.*\.png", + "subset": subset, + "startFrame": start, + "endFrame": end, + "fps": context.data.get("fps", None), + "families": ["render"], + "source": source, + "user": context.data["user"], + "version": context.data.get('version'), + "audio": context.data["audio"]['filename'], + # Optional metadata (for debugging) + "metadata": { + "instance": data, + "job": job, + "session": fake_avalon_session(project, projects_path) + } + } + + # Ensure output dir exists + output_dir = instance.data["outputDir"] + + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + + for k, v in metadata.items(): + self.log.info(k) + self.log.info(v) + + metadata_filename = "{}_metadata.json".format(subset) + metadata_path = os.path.join(output_dir, metadata_filename) + with open(metadata_path, "w") as f: + json.dump(metadata, f, indent=4, sort_keys=True) + + # Generate the payload for Deadline submission + payload = { + "JobInfo": { + "Plugin": "Python", + "BatchName": job["Props"]["Batch"], + "Name": job_name, + "JobType": "Normal", + "Group": "celaction", + "JobDependency0": job["_id"], + "UserName": os.environ['USERNAME'], + "Comment": instance.context.data.get("comment", ""), + "InitialStatus": "Active" + }, + "PluginInfo": { + "Version": "3.6", + "ScriptFile": r"\\pype\Core\dev\pype-setup\repos\pype-config\pype\scripts\publish_filesequence.py", + "Arguments": '--path "{}"'.format(metadata_path), + "SingleFrameOnly": "True" + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + # Transfer the environment from the original job to this dependent + # job so they use the same environment + environment = fake_env() + environment["AVALON_ASSET"] = asset + environment["AVALON_TASK"] = instance.context.data["ftrackData"]['Task']['name'] + environment["AVALON_PROJECT"] = project + environment["AVALON_PROJECTS"] = projects_path + environment["PYPE_STUDIO_PROJECTS_PUBLISH"] = ft_data['Project']['root'] + environment["PYPE_STUDIO_PROJECTS_RENDER"] = ft_data['Project']['root'] + environment["PYPE_STUDIO_PROJECTS_RESOURCES"] = ft_data['Project']['root'] + environment["PYPE_STUDIO_PROJECTS_WORK"] = ft_data['Project']['root'] + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + # Avoid copied pools and remove secondary pool + payload["JobInfo"]["Pool"] = "animation_2d" + payload["JobInfo"].pop("SecondaryPool", None) + + self.log.info("Submitting..") + # self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + + ################ + ###################### + fake_instance = instance.context.create_instance( + name=(str(instance) + "1")) + + for k, v in data.items(): + self.log.info(k) + fake_instance.data[k] = v + + # fake_instance.data['deadlineData'] = payload + # 'http://192.168.146.8:8082' + url = "{}/api/jobs".format('http://192.168.146.8:8082') + response = requests.post(url, json=payload) + if not response.ok: + raise Exception(response.text) + ###################### + ####################### + + +def fake_avalon_session(project=None, projects_path=None): + return { + "AVALON_APP": "premiere", + "AVALON_APP_VERSION": "2019", + "AVALON_ASSET": "editorial", + "AVALON_CONFIG": "pype", + "AVALON_CONTAINER_ID": "avalon.container", + "AVALON_DB": "Pype", + "AVALON_DEADLINE": "http://192.168.146.8:8082", + "AVALON_DEBUG": "1", + "AVALON_EARLY_ADOPTER": "1", + "AVALON_INSTANCE_ID": "avalon.instance", + "AVALON_LABEL": "Avalon", + "AVALON_LOCATION": "http://127.0.0.1", + "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", + "AVALON_PASSWORD": "secret", + "AVALON_PROJECT": project or "LBB2_dev", + "AVALON_PROJECTS": projects_path or "L:/PYPE_test", + "AVALON_SILO": "editorial", + "AVALON_TASK": "conform", + "AVALON_TIMEOUT": "1000", + "AVALON_USERNAME": "avalon", + "AVALON_WORKDIR": "L:/PYPE_test/episodes/editorial/work/conform", + "schema": "avalon-core:session-1.0" + } + + +def fake_env(): + return { + "AVALON_CONFIG": "pype", + "AVALON_CONTAINER_ID": "avalon.container", + "AVALON_CORE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core", + "AVALON_DB": "Pype", + "AVALON_DB_DATA": "\\\\pype\\Core\\dev\\mongo_db_data", + "AVALON_DEADLINE": "http://192.168.146.8:8082", + "AVALON_DEBUG": "1", + "AVALON_EARLY_ADOPTER": "1", + "AVALON_ENV_NAME": "pype_env", + "AVALON_HIERARCHY": "", + "AVALON_INSTANCE_ID": "avalon.instance", + "AVALON_LABEL": "Avalon", + "AVALON_LAUNCHER": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher", + "AVALON_LOCATION": "http://127.0.0.1", + "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", + "AVALON_MONGO_PORT": "27072", + "AVALON_PASSWORD": "secret", + "AVALON_SCHEMA": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\schema", + "AVALON_SILO": "", + "AVALON_TIMEOUT": "1000", + "AVALON_USERNAME": "avalon", + "AVALON_WORKDIR": "default", + "DEADLINE_PATH": "C:\\Program Files\\Thinkbox\\Deadline10\\bin", + "DEADLINE_REST_URL": "http://192.168.146.8:8082", + "FTRACK_API_KEY": "NGI0ZGU3ZjMtNzNiZC00NGVlLWEwY2EtMzA1OWJlZGM0MjAyOjozZWZmMThjZi04MjkwLTQxMzQtODUwMC03NTZhMGJiZTM2MTA", + "FTRACK_API_USER": "license@clothcatanimation.com", + "FTRACK_SERVER": "https://clothcat2.ftrackapp.com", + "MONGO_DB_ENTRYDB": "Pype", + "MONGO_DB_PASS": "X34vkuwL4wbK9A7X", + "MONGO_DB_USER": "PypeAdmin", + "PATH": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Scripts;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library;;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin\\windows;\\\\pype\\Core\\dev\\pype-setup\\app;\\\\pype\\core\\software\\ffmpeg\\bin;\\\\pype\\Core\\dev\\Applications\\djv\\bin", + "PYBLISHPLUGINPATH": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\plugins\\ftrack\\publish;", + "PYBLISH_BASE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base", + "PYBLISH_HOSTS": "shell", + "PYBLISH_LITE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite", + "PYBLISH_QML": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml", + "PYPE_APP_ROOT": "\\\\pype\\Core\\dev\\pype-setup\\app", + "PYPE_DEBUG": "3", + "PYPE_DEBUG_STDOUT": "0", + "PYPE_SETUP_ROOT": "\\\\pype\\Core\\dev\\pype-setup", + "PYPE_STUDIO_CODE": "CC", + "PYPE_STUDIO_CONFIG": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config", + "PYPE_STUDIO_CORE": "\\\\pype\\Core\\dev", + "PYPE_STUDIO_CORE_MOUNT": "\\\\pype\\Core\\dev", + "PYPE_STUDIO_NAME": "Cloth Cat", + "PYPE_STUDIO_SOFTWARE": "\\\\pype\\Core\\dev\\Applications", + "PYPE_STUDIO_TEMPLATES": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates", + "PYPE_STUDIO_TOOLS": "\\\\pype\\Core\\dev\\production\\tools", + "PYTHONPATH": "\\\\pype\\Core\\dev\\pype-setup;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server", + "PYTHONVERBOSE": "True", + "PYTHON_ENV": "C:\\Users\\Public\\pype_env", + "REMOTE_ENV_DIR": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env", + "REMOTE_ENV_ON": "0", + "SCHEMA": "avalon-core:session-1.0", + "STUDIO_SOFT": "\\\\evo2\\core\\Applications", + "TOOL_ENV": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\environments", + "USERNAME": "pype" + } diff --git a/pype/plugins/celaction/publish/validate_celaction_scene_path.py b/pype/plugins/celaction/_unused_publish/validate_celaction_scene_path.py similarity index 96% rename from pype/plugins/celaction/publish/validate_celaction_scene_path.py rename to pype/plugins/celaction/_unused_publish/validate_celaction_scene_path.py index a73819b9ca..17365178ac 100644 --- a/pype/plugins/celaction/publish/validate_celaction_scene_path.py +++ b/pype/plugins/celaction/_unused_publish/validate_celaction_scene_path.py @@ -1,62 +1,62 @@ -import shutil -import pyblish.api -import pyblish_standalone -import os -from bait.paths import get_env_work_file - - -class RepairCelactionScenePath(pyblish.api.Action): - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - - # get version data - version = context.data('version') if context.has_data('version') else 1 - - task_id = context.data["ftrackData"]["Task"]["id"] - expected_path = get_env_work_file( - "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') - - src = context.data["currentFile"] - - if not os.path.exists(os.path.dirname(expected_path)): - os.makedirs(os.path.dirname(expected_path)) - - if os.path.exists(os.path.dirname(expected_path)): - self.log.info("existing to \"%s\"" % expected_path) - - if os.path.exists(expected_path) and ('v001' in expected_path): - os.remove(expected_path) - - shutil.copy2(src, expected_path) - - pyblish_standalone.kwargs['path'] = [expected_path] - context.data["currentFile"] = expected_path - - self.log.info("Saved to \"%s\"" % expected_path) - - -class ValidateCelactionScenePath(pyblish.api.InstancePlugin): - order = pyblish.api.ValidatorOrder - families = ['scene'] - label = 'Scene Path' - actions = [RepairCelactionScenePath] - - def process(self, instance): - - # getting current work file - current_scene_path = pyblish_standalone.kwargs['path'][0] - - version = instance.context.data( - 'version') if instance.context.has_data('version') else 1 - - task_id = instance.context.data["ftrackData"]["Task"]["id"] - expected_scene_path = get_env_work_file( - "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') - - msg = 'Scene path is not correct: Current: {}, Expected: {}'.format( - current_scene_path, expected_scene_path) - - assert expected_scene_path == current_scene_path, msg +import shutil +import pyblish.api +import pyblish_standalone +import os +from bait.paths import get_env_work_file + + +class RepairCelactionScenePath(pyblish.api.Action): + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + + # get version data + version = context.data('version') if context.has_data('version') else 1 + + task_id = context.data["ftrackData"]["Task"]["id"] + expected_path = get_env_work_file( + "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') + + src = context.data["currentFile"] + + if not os.path.exists(os.path.dirname(expected_path)): + os.makedirs(os.path.dirname(expected_path)) + + if os.path.exists(os.path.dirname(expected_path)): + self.log.info("existing to \"%s\"" % expected_path) + + if os.path.exists(expected_path) and ('v001' in expected_path): + os.remove(expected_path) + + shutil.copy2(src, expected_path) + + pyblish_standalone.kwargs['path'] = [expected_path] + context.data["currentFile"] = expected_path + + self.log.info("Saved to \"%s\"" % expected_path) + + +class ValidateCelactionScenePath(pyblish.api.InstancePlugin): + order = pyblish.api.ValidatorOrder + families = ['scene'] + label = 'Scene Path' + actions = [RepairCelactionScenePath] + + def process(self, instance): + + # getting current work file + current_scene_path = pyblish_standalone.kwargs['path'][0] + + version = instance.context.data( + 'version') if instance.context.has_data('version') else 1 + + task_id = instance.context.data["ftrackData"]["Task"]["id"] + expected_scene_path = get_env_work_file( + "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') + + msg = 'Scene path is not correct: Current: {}, Expected: {}'.format( + current_scene_path, expected_scene_path) + + assert expected_scene_path == current_scene_path, msg From 950644f630166e5cb2f566cf277a68a8f32fedf9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Apr 2020 15:50:50 +0200 Subject: [PATCH 14/40] feat(celaction): wip publishing workfiles --- .../celaction/publish/collect_celaction_render.py | 4 +--- pype/plugins/celaction/publish/integrate_version_up.py | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pype/plugins/celaction/publish/collect_celaction_render.py b/pype/plugins/celaction/publish/collect_celaction_render.py index e6761633f9..2bf0309df8 100644 --- a/pype/plugins/celaction/publish/collect_celaction_render.py +++ b/pype/plugins/celaction/publish/collect_celaction_render.py @@ -18,12 +18,10 @@ class CollectCelactionRender(pyblish.api.ContextPlugin): instance = context.create_instance(name=component_name) instance.data["family"] = "render" instance.data["label"] = "{} - remote".format(component_name) - # instance.data["families"] = ["deadline", "remote", "img"] instance.data["families"] = ["render", "img"] - instance.data["managed_location"] = False # getting instance state - instance.data["publish"] = False + instance.data["publish"] = True data = context.data("kwargs")["data"] diff --git a/pype/plugins/celaction/publish/integrate_version_up.py b/pype/plugins/celaction/publish/integrate_version_up.py index 77c0d7a88b..7fb1efa8aa 100644 --- a/pype/plugins/celaction/publish/integrate_version_up.py +++ b/pype/plugins/celaction/publish/integrate_version_up.py @@ -1,5 +1,4 @@ import shutil -import os import re import pyblish.api @@ -30,12 +29,12 @@ def version_get(string, prefix, suffix=None): if string is None: raise ValueError("Empty version string - no match") - regex = "[/_.]" + prefix + "\d+" + regex = r"[/_.]{}\d+".format(prefix) matches = re.findall(regex, string, re.IGNORECASE) if not len(matches): - msg = "No \"_" + prefix + "#\" found in \"" + string + "\"" + msg = f"No `_{prefix}#` found in `{string}`" raise ValueError(msg) - return (matches[-1:][0][1], re.search("\d+", matches[-1:][0]).group()) + return (matches[-1:][0][1], re.search(r"\d+", matches[-1:][0]).group()) def version_set(string, prefix, oldintval, newintval): @@ -44,7 +43,7 @@ def version_set(string, prefix, oldintval, newintval): we use "v" for render version and "c" for camera track version. See the version.py and camera.py plugins for usage.""" - regex = "[/_.]" + prefix + "\d+" + regex = r"[/_.]{}\d+".format(prefix) matches = re.findall(regex, string, re.IGNORECASE) if not len(matches): return "" From 13dda8ef2412237f73cdfc9023eec371a7218fd8 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Apr 2020 15:33:09 +0100 Subject: [PATCH 15/40] celaction publishing wip --- .../collect_celaction_scene.py | 0 .../_unused_publish/collect_render_path.py | 60 +++++++++++++++++++ .../publish/collect_celaction_render.py | 24 +++++++- .../celaction/publish/collect_render_path.py | 59 +++++------------- 4 files changed, 95 insertions(+), 48 deletions(-) rename pype/plugins/celaction/{publish => _unused_publish}/collect_celaction_scene.py (100%) create mode 100644 pype/plugins/celaction/_unused_publish/collect_render_path.py diff --git a/pype/plugins/celaction/publish/collect_celaction_scene.py b/pype/plugins/celaction/_unused_publish/collect_celaction_scene.py similarity index 100% rename from pype/plugins/celaction/publish/collect_celaction_scene.py rename to pype/plugins/celaction/_unused_publish/collect_celaction_scene.py diff --git a/pype/plugins/celaction/_unused_publish/collect_render_path.py b/pype/plugins/celaction/_unused_publish/collect_render_path.py new file mode 100644 index 0000000000..72adf57e86 --- /dev/null +++ b/pype/plugins/celaction/_unused_publish/collect_render_path.py @@ -0,0 +1,60 @@ +""" +Requires: + context -> anatomy + context -> anatomyData + +Provides: + instance -> publishDir + instance -> resourcesDir +""" + +import os +import copy + +import pyblish.api +from avalon import api + + +class CollectRenderPath(pyblish.api.InstancePlugin): + """Generate file and directory path where rendered images will be""" + + label = "Collect Render Path" + order = pyblish.api.CollectorOrder + 0.495 + + def process(self, instance): + anatomy = instance.context.data["anatomy"] + + template_data = copy.deepcopy(instance.data["anatomyData"]) + + # This is for cases of Deprecated anatomy without `folder` + # TODO remove when all clients have solved this issue + template_data.update({ + "frame": "FRAME_TEMP", + "representation": "png" + }) + + anatomy_filled = anatomy.format(template_data) + + if "folder" in anatomy.templates["render"]: + render_folder = anatomy_filled["render"]["folder"] + render_file = anatomy_filled["render"]["file"] + else: + # solve deprecated situation when `folder` key is not underneath + # `publish` anatomy + project_name = api.Session["AVALON_PROJECT"] + self.log.warning(( + "Deprecation warning: Anatomy does not have set `folder`" + " key underneath `publish` (in global of for project `{}`)." + ).format(project_name)) + + file_path = anatomy_filled["render"]["path"] + # Directory + render_folder = os.path.dirname(file_path) + render_file = os.path.basename(file_path) + + render_folder = os.path.normpath(render_folder) + render_path = os.path.join(render_folder, render_file) + + instance.data["outputRenderPath"] = render_path + + self.log.debug("outputRenderPath: \"{}\"".format(render_path)) diff --git a/pype/plugins/celaction/publish/collect_celaction_render.py b/pype/plugins/celaction/publish/collect_celaction_render.py index 2bf0309df8..a8154e658f 100644 --- a/pype/plugins/celaction/publish/collect_celaction_render.py +++ b/pype/plugins/celaction/publish/collect_celaction_render.py @@ -6,9 +6,12 @@ import pyblish.api class CollectCelactionRender(pyblish.api.ContextPlugin): """ Adds the celaction render instances """ + label = "Collect Celaction Render Instance" order = pyblish.api.CollectorOrder + 0.1 def process(self, context): + project_entity = context.data["projectEntity"] + asset_entity = context.data["assetEntity"] # scene render scene_file = os.path.basename(context.data["currentFile"]) @@ -23,7 +26,22 @@ class CollectCelactionRender(pyblish.api.ContextPlugin): # getting instance state instance.data["publish"] = True - data = context.data("kwargs")["data"] + # add assetEntity data into instance + instance.data.update({ + "subset": "renderAnimationMain", + "asset": asset_entity["name"], + "frameStart": asset_entity["data"]["frameStart"], + "frameEnd": asset_entity["data"]["frameEnd"], + "handleStart": asset_entity["data"]["handleStart"], + "handleEnd": asset_entity["data"]["handleEnd"], + "fps": asset_entity["data"]["fps"], + "resolutionWidth": asset_entity["data"]["resolutionWidth"], + "resolutionHeight": asset_entity["data"]["resolutionHeight"], + "pixelAspect": 1, + "step": 1 + }) - for item in data: - instance.set_data(item, value=data[item]) + data = context.data.get("kwargs", {}).get("data", {}) + + if data: + instance.data.update(data) diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py index 72adf57e86..a0d82fe4a5 100644 --- a/pype/plugins/celaction/publish/collect_render_path.py +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -1,18 +1,6 @@ -""" -Requires: - context -> anatomy - context -> anatomyData - -Provides: - instance -> publishDir - instance -> resourcesDir -""" - import os -import copy - import pyblish.api -from avalon import api + class CollectRenderPath(pyblish.api.InstancePlugin): @@ -22,39 +10,20 @@ class CollectRenderPath(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.495 def process(self, instance): - anatomy = instance.context.data["anatomy"] + current_file = instance.context.data["currentFile"] + work_dir = os.path.dirname(current_file) + work_file = os.path.basename(current_file) - template_data = copy.deepcopy(instance.data["anatomyData"]) + render_dir = os.path.join( + work_dir, "render", "celaction" + ) + render_path = os.path.join( + render_dir, ".".join([instance.data["subset"], "%05d", "png"]) + ) - # This is for cases of Deprecated anatomy without `folder` - # TODO remove when all clients have solved this issue - template_data.update({ - "frame": "FRAME_TEMP", - "representation": "png" - }) + # create dir if it doesnt exists + os.makedirs(render_dir, exist_ok=True) - anatomy_filled = anatomy.format(template_data) + instance.data["path"] = render_path - if "folder" in anatomy.templates["render"]: - render_folder = anatomy_filled["render"]["folder"] - render_file = anatomy_filled["render"]["file"] - else: - # solve deprecated situation when `folder` key is not underneath - # `publish` anatomy - project_name = api.Session["AVALON_PROJECT"] - self.log.warning(( - "Deprecation warning: Anatomy does not have set `folder`" - " key underneath `publish` (in global of for project `{}`)." - ).format(project_name)) - - file_path = anatomy_filled["render"]["path"] - # Directory - render_folder = os.path.dirname(file_path) - render_file = os.path.basename(file_path) - - render_folder = os.path.normpath(render_folder) - render_path = os.path.join(render_folder, render_file) - - instance.data["outputRenderPath"] = render_path - - self.log.debug("outputRenderPath: \"{}\"".format(render_path)) + self.log.info(f"Render output path set to: `{render_path}`") From d30df3a75a38221e7df8191333fbeedd74854c4f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Apr 2020 16:34:55 +0100 Subject: [PATCH 16/40] feat(celaction): publishing wip --- pype/celaction/cli.py | 2 + .../collect_data.py | 0 ...rgs.py => collect_celaction_cli_kwargs.py} | 13 +-- .../publish/collect_celaction_instances.py | 85 +++++++++++++++++++ .../publish/collect_celaction_render.py | 47 ---------- .../publish/submit_celaction_deadline.py | 4 +- .../global/publish/submit_publish_job.py | 2 +- 7 files changed, 98 insertions(+), 55 deletions(-) rename pype/plugins/celaction/{publish => _unused_publish}/collect_data.py (100%) rename pype/plugins/celaction/publish/{collect_kwargs.py => collect_celaction_cli_kwargs.py} (50%) create mode 100644 pype/plugins/celaction/publish/collect_celaction_instances.py delete mode 100644 pype/plugins/celaction/publish/collect_celaction_render.py diff --git a/pype/celaction/cli.py b/pype/celaction/cli.py index f6d518a5a9..ade15effdf 100644 --- a/pype/celaction/cli.py +++ b/pype/celaction/cli.py @@ -108,6 +108,8 @@ def main(): log.info(f"Registering path: {path}") pyblish.api.register_plugin_path(path) + pyblish.api.register_host(publish_host) + # Register project specific plugins project_name = os.environ["AVALON_PROJECT"] project_plugins_paths = os.getenv("PYPE_PROJECT_PLUGINS", "") diff --git a/pype/plugins/celaction/publish/collect_data.py b/pype/plugins/celaction/_unused_publish/collect_data.py similarity index 100% rename from pype/plugins/celaction/publish/collect_data.py rename to pype/plugins/celaction/_unused_publish/collect_data.py diff --git a/pype/plugins/celaction/publish/collect_kwargs.py b/pype/plugins/celaction/publish/collect_celaction_cli_kwargs.py similarity index 50% rename from pype/plugins/celaction/publish/collect_kwargs.py rename to pype/plugins/celaction/publish/collect_celaction_cli_kwargs.py index b2e6d97b8b..d60eaba4c6 100644 --- a/pype/plugins/celaction/publish/collect_kwargs.py +++ b/pype/plugins/celaction/publish/collect_celaction_cli_kwargs.py @@ -2,16 +2,19 @@ import pyblish.api import pype.celaction -class CollectKwargs(pyblish.api.Collector): +class CollectCelactionCliKwargs(pyblish.api.Collector): """ Collects all keyword arguments passed from the terminal """ - + + label = "Collect Celaction Cli Kwargs" order = pyblish.api.Collector.order - 0.1 def process(self, context): kwargs = pype.celaction.kwargs.copy() - self.log.info("Converting nested lists to dict: %s" % kwargs) - kwargs["data"] = dict(kwargs.get("data") or []) - self.log.info("Storing kwargs: %s" % kwargs) context.set_data("kwargs", kwargs) + + # get kwargs onto context data as keys with values + for k, v in kwargs.items(): + self.log.info(f"Setting `{k}` to instance.data with value: `{v}`") + context.data[k] = v diff --git a/pype/plugins/celaction/publish/collect_celaction_instances.py b/pype/plugins/celaction/publish/collect_celaction_instances.py new file mode 100644 index 0000000000..84e4ded303 --- /dev/null +++ b/pype/plugins/celaction/publish/collect_celaction_instances.py @@ -0,0 +1,85 @@ +import os +from avalon import api +import pyblish.api + + +class CollectCelactionInstances(pyblish.api.ContextPlugin): + """ Adds the celaction render instances """ + + label = "Collect Celaction Instances" + order = pyblish.api.CollectorOrder + 0.1 + + def process(self, context): + task = api.Session["AVALON_TASK"] + current_file = context.data["currentFile"] + staging_dir = os.path.dirname(current_file) + scene_file = os.path.basename(current_file) + + asset_entity = context.data["assetEntity"] + + shared_instance_data = { + "asset": asset_entity["name"], + "frameStart": asset_entity["data"]["frameStart"], + "frameEnd": asset_entity["data"]["frameEnd"], + "handleStart": asset_entity["data"]["handleStart"], + "handleEnd": asset_entity["data"]["handleEnd"], + "fps": asset_entity["data"]["fps"], + "resolutionWidth": asset_entity["data"]["resolutionWidth"], + "resolutionHeight": asset_entity["data"]["resolutionHeight"], + "pixelAspect": 1, + "step": 1 + } + + celaction_kwargs = context.data.get("kwargs", {}) + + if celaction_kwargs: + shared_instance_data.update(celaction_kwargs) + + ##################################################3 + # workfile instance + family = "workfile" + subset = family + task.capitalize() + # Create instance + instance = context.create_instance(subset) + + # creating instance data + instance.data.update({ + "subset": subset, + "label": scene_file, + "family": family, + "families": [], + "representations": list() + }) + + # adding basic script data + instance.data.update(shared_instance_data) + + # creating representation + representation = { + 'name': 'scn', + 'ext': 'scn', + 'files': scene_file, + "stagingDir": staging_dir, + } + + instance.data["representations"].append(representation) + + self.log.info('Publishing Celaction workfile') + context.data["instances"].append(instance) + + ####################################################3 + # render instance + subset = f"render{task}Main" + instance = context.create_instance(name=subset) + # getting instance state + instance.data["publish"] = True + + # add assetEntity data into instance + instance.data.update({ + "label": "{} - farm".format(subset), + "family": "render.farm", + "families": [], + "subset": + }) + + self.log.debug(f"Instance data: `{instance.data}`") diff --git a/pype/plugins/celaction/publish/collect_celaction_render.py b/pype/plugins/celaction/publish/collect_celaction_render.py deleted file mode 100644 index a8154e658f..0000000000 --- a/pype/plugins/celaction/publish/collect_celaction_render.py +++ /dev/null @@ -1,47 +0,0 @@ -import os - -import pyblish.api - - -class CollectCelactionRender(pyblish.api.ContextPlugin): - """ Adds the celaction render instances """ - - label = "Collect Celaction Render Instance" - order = pyblish.api.CollectorOrder + 0.1 - - def process(self, context): - project_entity = context.data["projectEntity"] - asset_entity = context.data["assetEntity"] - - # scene render - scene_file = os.path.basename(context.data["currentFile"]) - scene_name, _ = os.path.splitext(scene_file) - component_name = scene_name.split(".")[0] - - instance = context.create_instance(name=component_name) - instance.data["family"] = "render" - instance.data["label"] = "{} - remote".format(component_name) - instance.data["families"] = ["render", "img"] - - # getting instance state - instance.data["publish"] = True - - # add assetEntity data into instance - instance.data.update({ - "subset": "renderAnimationMain", - "asset": asset_entity["name"], - "frameStart": asset_entity["data"]["frameStart"], - "frameEnd": asset_entity["data"]["frameEnd"], - "handleStart": asset_entity["data"]["handleStart"], - "handleEnd": asset_entity["data"]["handleEnd"], - "fps": asset_entity["data"]["fps"], - "resolutionWidth": asset_entity["data"]["resolutionWidth"], - "resolutionHeight": asset_entity["data"]["resolutionHeight"], - "pixelAspect": 1, - "step": 1 - }) - - data = context.data.get("kwargs", {}).get("data", {}) - - if data: - instance.data.update(data) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index 3ea9b929e3..d2a62c2b0f 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -19,7 +19,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): label = "Submit CelAction to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["celaction"] - families = ["render"] + families = ["render.farm"] deadline_department = "" deadline_priority = 50 @@ -86,7 +86,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): pass # define chunk and priority - chunk_size = instance.data.get("deadlineChunkSize") + chunk_size = instance.context.data.get("chunk") if chunk_size == 0: chunk_size = self.deadline_chunk_size diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 843760f9ec..2dec6e090b 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -139,7 +139,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.2 icon = "tractor" - hosts = ["fusion", "maya", "nuke"] + hosts = ["fusion", "maya", "nuke", "celaction"] families = ["render.farm", "prerener", "renderlayer", "imagesequence"] From cb57b839e3f47bc3cd3358fc471bb4223c9cdcae Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Apr 2020 17:37:35 +0200 Subject: [PATCH 17/40] feat(celaction): wip publishing --- .../celaction/publish/collect_celaction_instances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/celaction/publish/collect_celaction_instances.py b/pype/plugins/celaction/publish/collect_celaction_instances.py index 84e4ded303..bf609ed90d 100644 --- a/pype/plugins/celaction/publish/collect_celaction_instances.py +++ b/pype/plugins/celaction/publish/collect_celaction_instances.py @@ -35,7 +35,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): if celaction_kwargs: shared_instance_data.update(celaction_kwargs) - ##################################################3 + # ___________________________________________ # workfile instance family = "workfile" subset = family + task.capitalize() @@ -67,7 +67,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): self.log.info('Publishing Celaction workfile') context.data["instances"].append(instance) - ####################################################3 + # ___________________________________________ # render instance subset = f"render{task}Main" instance = context.create_instance(name=subset) @@ -79,7 +79,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): "label": "{} - farm".format(subset), "family": "render.farm", "families": [], - "subset": + "subset": subset }) self.log.debug(f"Instance data: `{instance.data}`") From 25f5ff72e9db920a86ec5c09d7aac8b73fe10411 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 May 2020 15:46:52 +0100 Subject: [PATCH 18/40] feat(celaction): publishing wip --- pype/celaction/cli.py | 4 +- pype/hooks/celaction/prelaunch.py | 2 +- .../publish/collect_celaction_cli_kwargs.py | 7 +- .../publish/collect_celaction_instances.py | 23 +++++-- .../celaction/publish/collect_render_path.py | 5 +- .../publish/submit_celaction_deadline.py | 65 ++++++++++++------- 6 files changed, 70 insertions(+), 36 deletions(-) diff --git a/pype/celaction/cli.py b/pype/celaction/cli.py index ade15effdf..daa971d8d5 100644 --- a/pype/celaction/cli.py +++ b/pype/celaction/cli.py @@ -52,8 +52,8 @@ def cli(): parser.add_argument("--resolutionHeight", help=("Height of resolution")) - parser.add_argument("--programDir", - help=("Directory with celaction program installation")) + # parser.add_argument("--programDir", + # help=("Directory with celaction program installation")) pype.celaction.kwargs = parser.parse_args(sys.argv[1:]).__dict__ diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 9af3cdd740..2cd4b01381 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -88,7 +88,7 @@ class CelactionPrelaunchHook(PypeHook): "--frameEnd *END*", "--resolutionWidth *X*", "--resolutionHeight *Y*", - "--programDir \"'*PROGPATH*'\"" + # "--programDir \"'*PROGPATH*'\"" ] winreg.SetValueEx(hKey, "SubmitParametersTitle", 0, winreg.REG_SZ, " ".join(parameters)) diff --git a/pype/plugins/celaction/publish/collect_celaction_cli_kwargs.py b/pype/plugins/celaction/publish/collect_celaction_cli_kwargs.py index d60eaba4c6..5042a7b700 100644 --- a/pype/plugins/celaction/publish/collect_celaction_cli_kwargs.py +++ b/pype/plugins/celaction/publish/collect_celaction_cli_kwargs.py @@ -4,7 +4,7 @@ import pype.celaction class CollectCelactionCliKwargs(pyblish.api.Collector): """ Collects all keyword arguments passed from the terminal """ - + label = "Collect Celaction Cli Kwargs" order = pyblish.api.Collector.order - 0.1 @@ -17,4 +17,7 @@ class CollectCelactionCliKwargs(pyblish.api.Collector): # get kwargs onto context data as keys with values for k, v in kwargs.items(): self.log.info(f"Setting `{k}` to instance.data with value: `{v}`") - context.data[k] = v + if k in ["frameStart", "frameEnd"]: + context.data[k] = kwargs[k] = int(v) + else: + context.data[k] = v diff --git a/pype/plugins/celaction/publish/collect_celaction_instances.py b/pype/plugins/celaction/publish/collect_celaction_instances.py index bf609ed90d..583cb7514b 100644 --- a/pype/plugins/celaction/publish/collect_celaction_instances.py +++ b/pype/plugins/celaction/publish/collect_celaction_instances.py @@ -1,7 +1,7 @@ import os from avalon import api import pyblish.api - +from pype import api as pype class CollectCelactionInstances(pyblish.api.ContextPlugin): """ Adds the celaction render instances """ @@ -14,7 +14,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): current_file = context.data["currentFile"] staging_dir = os.path.dirname(current_file) scene_file = os.path.basename(current_file) - + version = context.data["version"] asset_entity = context.data["assetEntity"] shared_instance_data = { @@ -27,7 +27,8 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): "resolutionWidth": asset_entity["data"]["resolutionWidth"], "resolutionHeight": asset_entity["data"]["resolutionHeight"], "pixelAspect": 1, - "step": 1 + "step": 1, + "version": version } celaction_kwargs = context.data.get("kwargs", {}) @@ -47,7 +48,7 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): "subset": subset, "label": scene_file, "family": family, - "families": [], + "families": [family], "representations": list() }) @@ -65,10 +66,10 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): instance.data["representations"].append(representation) self.log.info('Publishing Celaction workfile') - context.data["instances"].append(instance) # ___________________________________________ # render instance + family = "render.farm" subset = f"render{task}Main" instance = context.create_instance(name=subset) # getting instance state @@ -77,9 +78,17 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): # add assetEntity data into instance instance.data.update({ "label": "{} - farm".format(subset), - "family": "render.farm", - "families": [], + "family": family, + "families": [family], "subset": subset }) + # adding basic script data + instance.data.update(shared_instance_data) + + self.log.info('Publishing Celaction render instance') self.log.debug(f"Instance data: `{instance.data}`") + + + for i in context: + self.log.debug(f"{i.data['families']}") diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py index a0d82fe4a5..32a4bf7c32 100644 --- a/pype/plugins/celaction/publish/collect_render_path.py +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -10,15 +10,16 @@ class CollectRenderPath(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.495 def process(self, instance): + anatomy = instance.context.data["anatomy"] current_file = instance.context.data["currentFile"] work_dir = os.path.dirname(current_file) work_file = os.path.basename(current_file) - + padding = anatomy.templates.get("frame_padding", 4) render_dir = os.path.join( work_dir, "render", "celaction" ) render_path = os.path.join( - render_dir, ".".join([instance.data["subset"], "%05d", "png"]) + render_dir, ".".join([instance.data["subset"], f"%0{padding}d", "png"]) ) # create dir if it doesnt exists diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index d2a62c2b0f..9e0739419b 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -33,17 +33,15 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): context = instance.context - DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL", - "http://localhost:8082") + DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL") assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" self.deadline_url = "{}/api/jobs".format(DEADLINE_REST_URL) self._comment = context.data.get("comment", "") - self._ver = re.search(r"\d+\.\d+", context.data.get("hostVersion")) self._deadline_user = context.data.get( "deadlineUser", getpass.getuser()) - self._frame_start = int(instance.data["frameStartHandle"]) - self._frame_end = int(instance.data["frameEndHandle"]) + self._frame_start = int(instance.data["frameStart"]) + self._frame_end = int(instance.data["frameEnd"]) # get output path render_path = instance.data['path'] @@ -60,9 +58,10 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): render_path).replace("\\", "/") instance.data["publishJobState"] = "Suspended" + instance.context.data['ftrackStatus'] = "Render" # adding 2d render specific family for version identification in Loader - instance.data["families"] = families.insert(0, "render2d") + instance.data["families"] = ["render2d"] def payload_submit(self, instance, @@ -70,6 +69,8 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): render_path, responce_data=None ): + resolution_width = instance.data["resolutionWidth"] + resolution_height = instance.data["resolutionHeight"] render_dir = os.path.normpath(os.path.dirname(render_path)) script_name = os.path.basename(script_path) jobname = "%s - %s" % (script_name, instance.name) @@ -90,14 +91,35 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): if chunk_size == 0: chunk_size = self.deadline_chunk_size + # search for %02d pattern in name, and padding number + search_results = re.search(r"(.%0)(\d)(d)[._]", render_path).groups() + split_patern = "".join(search_results) + padding_number = int(search_results[1]) + + args = [ + f"{script_path}", + "-a", + "-s ", + "-e ", + f"-d {render_dir}", + f"-x {resolution_width}", + f"-y {resolution_height}", + f"-r {render_path.replace(split_patern, '')}", + f"-= AbsoluteFrameNumber=on -= PadDigits={padding_number}", + "-= ClearAttachment=on", + ] + payload = { "JobInfo": { - # Top-level group name - "BatchName": script_name, - # Job name, as seen in Monitor "Name": jobname, + # plugin definition + "Plugin": "CelAction", + + # Top-level group name + "BatchName": script_name, + # Arbitrary username, for visualisation in Monitor "UserName": self._deadline_user, @@ -109,11 +131,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): "Pool": self.deadline_pool, "SecondaryPool": self.deadline_pool_secondary, - "Plugin": "CelAction", - "Frames": "{start}-{end}".format( - start=self._frame_start, - end=self._frame_end - ), + "Frames": f"{self._frame_start}-{self._frame_end}", "Comment": self._comment, # Optional, enable double-click to preview rendered @@ -125,11 +143,12 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): # Input "SceneFile": script_path, - # Output directory and filename + # Output directory "OutputFilePath": render_dir.replace("\\", "/"), - # Mandatory for Deadline - "Version": self._ver.group(), + # Plugin attributes + "StartupDirectory": "", + "Arguments": " ".join(args), # Resolve relative references "ProjectPath": script_path, @@ -176,14 +195,14 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): elif os.pathsep not in to_process: try: path = environment[key] - path.decode('UTF-8', 'strict') + path.encode().decode('UTF-8', 'strict') clean_path = os.path.normpath(path) except UnicodeDecodeError: print('path contains non UTF characters') else: for path in environment[key].split(os.pathsep): try: - path.decode('UTF-8', 'strict') + path.encode().decode('UTF-8', 'strict') clean_path += os.path.normpath(path) + os.pathsep except UnicodeDecodeError: print('path contains non UTF characters') @@ -253,9 +272,11 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): """ self.log.debug("_ path: `{}`".format(path)) if "%" in path: - search_results = re.search(r"(%0)(\d)(d.)", path).groups() - self.log.debug("_ search_results: `{}`".format(search_results)) - return int(search_results[1]) + search_results = re.search(r"[._](%0)(\d)(d)[._]", path).groups() + split_patern = "".join(search_results) + split_path = path.split(split_patern) + hashes = "#" * int(search_results[1]) + return "".join([split_path[0], hashes, split_path[-1]]) if "#" in path: self.log.debug("_ path: `{}`".format(path)) return path From 6d816a87b8ee08fb00c2438c1f133082aecdf2fc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 1 May 2020 17:02:45 +0200 Subject: [PATCH 19/40] feat(celaction): improve message --- pype/hooks/celaction/prelaunch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 2cd4b01381..497d40ace0 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -58,7 +58,7 @@ class CelactionPrelaunchHook(PypeHook): project_file = os.path.join(workdir, workfile) env["PYPE_CELACTION_PROJECT_FILE"] = project_file - self.log.info(f"Workfile is: `{project_file}`") + self.log.info(f"Workfile to open: `{project_file}`") ########################## # setting output parameters From af055612ca74f29aab419367f5c2ae7690c45958 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 May 2020 15:01:20 +0100 Subject: [PATCH 20/40] submiting from presets and env filtering --- .../publish/submit_celaction_deadline.py | 52 ++----------------- .../global/publish/submit_publish_job.py | 26 +++++++--- 2 files changed, 24 insertions(+), 54 deletions(-) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index 9e0739419b..1d7694b1f0 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -169,55 +169,13 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): # Include critical environment variables with submission keys = [ - "PYTHONPATH", - "PATH", - "AVALON_SCHEMA", - "PYBLISHPLUGINPATH", - "TOOL_ENV" + "FTRACK_API_USER", + "FTRACK_API_KEY", + "FTRACK_SERVER", + "AVALON_PROJECT" ] environment = dict({key: os.environ[key] for key in keys - if key in os.environ}, **api.Session) - - for path in os.environ: - if path.lower().startswith('pype_'): - environment[path] = os.environ[path] - - environment["PATH"] = os.environ["PATH"] - clean_environment = {} - for key in environment: - clean_path = "" - self.log.debug("key: {}".format(key)) - to_process = environment[key] - if key == "PYPE_STUDIO_CORE_MOUNT": - clean_path = environment[key] - elif "://" in environment[key]: - clean_path = environment[key] - elif os.pathsep not in to_process: - try: - path = environment[key] - path.encode().decode('UTF-8', 'strict') - clean_path = os.path.normpath(path) - except UnicodeDecodeError: - print('path contains non UTF characters') - else: - for path in environment[key].split(os.pathsep): - try: - path.encode().decode('UTF-8', 'strict') - clean_path += os.path.normpath(path) + os.pathsep - except UnicodeDecodeError: - print('path contains non UTF characters') - - if key == "PYTHONPATH": - clean_path = clean_path.replace('python2', 'python3') - - clean_path = clean_path.replace( - os.path.normpath( - environment['PYPE_STUDIO_CORE_MOUNT']), # noqa - os.path.normpath( - environment['PYPE_STUDIO_CORE_PATH'])) # noqa - clean_environment[key] = clean_path - - environment = clean_environment + if key in os.environ}) payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 2dec6e090b..3f5f9bc1e5 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -146,20 +146,23 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): aov_filter = {"maya": ["beauty"]} enviro_filter = [ - "PATH", - "PYTHONPATH", + # "PYTHONPATH", "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "PYPE_ROOT", + "PYPE_LOG_NO_COLORS", "PYPE_METADATA_FILE", - "PYPE_STUDIO_PROJECTS_PATH", - "PYPE_STUDIO_PROJECTS_MOUNT", - "AVALON_PROJECT" + "AVALON_PROJECT", + "PYPE_PYTHON_EXE", + "PYTHONPATH" ] - # pool used to do the publishing job + # custom deadline atributes + deadline_department = "" deadline_pool = "" + deadline_pool_secondary = "" + deadline_group = "" + deadline_chunk_size = 1 # regex for finding frame number in string R_FRAME_NUMBER = re.compile(r'.+\.(?P[0-9]+)\..+') @@ -206,8 +209,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "JobDependency0": job["_id"], "UserName": job["Props"]["User"], "Comment": instance.context.data.get("comment", ""), + + "Department": self.deadline_department, + "ChunkSize": self.deadline_chunk_size, "Priority": job["Props"]["Pri"], + + "Group": self.deadline_group, "Pool": self.deadline_pool, + "SecondaryPool": self.deadline_pool_secondary, + "OutputDirectory0": output_dir }, "PluginInfo": { @@ -223,6 +233,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Transfer the environment from the original job to this dependent # job so they use the same environment environment = job["Props"].get("Env", {}) + environment["PYPE_PYTHON_EXE"] = "//pype/Core/software/python36/python.exe" + environment["PYPE_LOG_NO_COLORS"] = "1" environment["PYPE_METADATA_FILE"] = metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] i = 0 From 7369e41e67c46751949bdf923559a005cd671092 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 May 2020 18:18:04 +0100 Subject: [PATCH 21/40] feat(global): adding submission with audio to metadata.json --- pype/plugins/global/publish/submit_publish_job.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 3f5f9bc1e5..aa92a55c1a 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -146,15 +146,13 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): aov_filter = {"maya": ["beauty"]} enviro_filter = [ - # "PYTHONPATH", "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", "PYPE_LOG_NO_COLORS", "PYPE_METADATA_FILE", "AVALON_PROJECT", - "PYPE_PYTHON_EXE", - "PYTHONPATH" + "PYPE_PYTHON_EXE" ] # custom deadline atributes @@ -749,6 +747,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "instances": instances } + # add audio to metadata file if available + audio_file = context.data.get("audioFile") + if os.path.isfile(audio_file): + publish_job.update({ "audio": audio_file}) + # pass Ftrack credentials in case of Muster if submission_type == "muster": ftrack = { From 0a60e5fd5d769907be82eb355b851b7e554aa02a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 May 2020 18:18:45 +0100 Subject: [PATCH 22/40] feat(celaction): collecting audio representation path --- .../celaction/publish/collect_audio.py | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/pype/plugins/celaction/publish/collect_audio.py b/pype/plugins/celaction/publish/collect_audio.py index bf4e1f47f3..44b40f046c 100644 --- a/pype/plugins/celaction/publish/collect_audio.py +++ b/pype/plugins/celaction/publish/collect_audio.py @@ -1,38 +1,41 @@ import pyblish.api -from bait.paths import get_output_path import os +import pype.api as pype +from avalon import io +from pprint import pformat class AppendCelactionAudio(pyblish.api.ContextPlugin): - label = "Pype Audio" + label = "Colect Audio for publishing" order = pyblish.api.CollectorOrder + 0.1 def process(self, context): self.log.info('Collecting Audio Data') - version = context.data('version') if context.has_data('version') else 1 + asset_entity = context.data["assetEntity"] + asset_id = asset_entity["_id"] - task_id = context.data["ftrackData"]["Task"]["id"] + # get all available representations + subsets = pype.get_subsets(asset_entity["name"], + representations=["audio"] + ) + self.log.info(f"subsets is: {pformat(subsets)}") - component_name = context.data["ftrackData"]['Shot']['name'] - version = context.data["version"] + if not subsets.get("audioMain"): + raise AttributeError("`audioMain` subset does not exist") - publish_path = get_output_path( - task_id, component_name, version, "mov").split('/')[0:-4] + reprs = subsets.get("audioMain", {}).get("representations", []) + self.log.info(f"reprs is: {pformat(reprs)}") - self.log.info('publish_path: {}'.format(publish_path)) + repr = next((r for r in reprs), None) + if not repr: + raise "Missing `audioMain` representation" + self.log.info(f"represetation is: {repr}") - audio_file = '/'.join(publish_path + [ - 'audio', - 'audioMain', - component_name + '_audioMain_v001.wav' - ]) + audio_file = repr.get('data', {}).get('path', "") if os.path.exists(audio_file): - context.data["audio"] = { - 'filename': audio_file, - 'enabled': True - } + context.data["audioFile"] = audio_file self.log.info( 'audio_file: {}, has been added to context'.format(audio_file)) else: From 44175bab45ee9be3516d36060d32dae2dcbd2e30 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 May 2020 18:19:15 +0100 Subject: [PATCH 23/40] typo(pype): fixing typo in attribute --- pype/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/lib.py b/pype/lib.py index 2bd18dacff..247611f207 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -644,7 +644,7 @@ def get_subsets(asset_name, if len(repres_out) > 0: output_dict[subset["name"]] = {"version": version_sel, - "representaions": repres_out} + "representations": repres_out} return output_dict From 9b50845cfa9a30303928e9df212b6ba5dd65f74b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 May 2020 18:20:40 +0100 Subject: [PATCH 24/40] feat(global): collecting audio to instance from metadata.json --- pype/plugins/global/publish/collect_rendered_files.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 8ecf7ba156..381f6af372 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -75,6 +75,16 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): data=r) for r in instance.get("representations")] i.data.update(instance) + # add audio if in metadata data + if data.get("audio"): + i.data.update({ + "audio": [{ + "filename": data.get("audio"), + "offset": 0 + }] + }) + self.log.info(f"Adding audio to instance: {i.data['audio']}") + def process(self, context): self._context = context From f2473391b865f5f9a71d865d8de4da3ff2333369 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 5 May 2020 18:21:30 +0100 Subject: [PATCH 25/40] clean(celaction): arbitrary code --- .../publish/submit_celaction_deadline.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index 1d7694b1f0..7b4366ca24 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -66,8 +66,7 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): def payload_submit(self, instance, script_path, - render_path, - responce_data=None + render_path ): resolution_width = instance.data["resolutionWidth"] resolution_height = instance.data["resolutionHeight"] @@ -77,9 +76,6 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): output_filename_0 = self.preview_fname(render_path) - if not responce_data: - responce_data = {} - try: # Ensure render folder exists os.makedirs(render_dir) @@ -125,11 +121,11 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): "Department": self.deadline_department, "Priority": self.deadline_priority, - "ChunkSize": chunk_size, "Group": self.deadline_group, "Pool": self.deadline_pool, "SecondaryPool": self.deadline_pool_secondary, + "ChunkSize": chunk_size, "Frames": f"{self._frame_start}-{self._frame_end}", "Comment": self._comment, @@ -159,14 +155,6 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): "AuxFiles": [] } - if responce_data.get("_id"): - payload["JobInfo"].update({ - "JobType": "Normal", - "BatchName": responce_data["Props"]["Batch"], - "JobDependency0": responce_data["_id"], - "ChunkSize": 99999999 - }) - # Include critical environment variables with submission keys = [ "FTRACK_API_USER", From c51b071a6452341bd6260a6f70d053021607cc81 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 3 Jun 2020 10:55:33 +0100 Subject: [PATCH 26/40] feat(celaction): create workfile from template if none --- pype/celaction/celaction_template_scene.scn | Bin 0 -> 5151 bytes pype/hooks/celaction/prelaunch.py | 50 ++++++++++++++------ 2 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 pype/celaction/celaction_template_scene.scn diff --git a/pype/celaction/celaction_template_scene.scn b/pype/celaction/celaction_template_scene.scn new file mode 100644 index 0000000000000000000000000000000000000000..54e4497a31deb3dcbdac278364f13289386c2b62 GIT binary patch literal 5151 zcmeHLUuauZ7(b~sYqQFrjvAcRJ z+7Q@-h=_uF5fo&0F#N|55kX{uihCN2^+ni&e^Yx%ZxX7ZMNO8I7rt7-MWJxDCubjNw-!&CID8D?68eD9D3pE6qo;Ig@j3 zuJF=)_SqSW@6{gUH4qP5Q?<~h$}VGEI_ON5hGbp6HhL_*I zm^^ggk%5DnrqVihfi-_>%6u_Z$QI08HgBe;t@(83xs+wj7IJCJJetawxwI(!fuor` z~7e5z;b?W9d1?t zSE_X1uiU0F$-j}#fA#qDLA5z*`Y0pWUa*{5I@)I3L-b2XGSDgH$Uq}9K*zEOQ`>k!0 zZ69Cy!0v^94cL3C3C?#7Iti+_TWCw?=BE%p-LYivgXHKeQT{DUwgE{w3GbdWt%K%C z{&ycZZvA+@*QWVeTdZ=$1EGZF+_y>J~$3ps$FVOD@Lk$o4)OgGvG^f)q z1^F9hY7_$;EP76OIOEhf%-(u?+b=T;11=dIA9n= zga(XoY{+M%DGsfko;7Px0c`@w1N}l!8~_B0%~`->@O}{tK@%0rOsEmM(nQI7u*~(& z`nHoxg-2FMxUSv1A|!1Zr=xVZhqfMUU(F{V)n@eT{cf|R-*OdLU0oG>h4xGBlKXa1 z`Rq2vu0B;%_-jSw((Sk%JY7@<;V0Zirc1Mg8^jM{)0Gax0?R$f7{WJEvwP9{H;c+9 zVAWBZ8|F0pYEgLuW1W7os7xZ}P3Xq=&FAJt{D!?F`+ z!v#ojPlhC)y!S#CgohsM2(QsCm}of29Nm4Kce?8BI^p#-Zwaz{e9c>aV$C}MCPWul zj)y0!c-BLnp}B=74OI_DS)agMkz-PrILi`7Q^!LQHOjWb=VhO;9Y`kgQEqX}k3UKw zY&V$twpAsBAc-fV8NLj^8$&q8tg;X+UzLuvxs_|$+|%g!CtST>&V|kqk{WYKi_Rtz zEq%@!M bool: + + from pprint import pformat + self.log.info(f"`{pformat(env)}`") if not env: - self.env = os.environ - else: - self.env = env + env = os.environ + # initialize self._S = api.Session - project = self._S["AVALON_PROJECT"] = self.env["AVALON_PROJECT"] - asset = self._S["AVALON_ASSET"] = self.env["AVALON_ASSET"] - task = self._S["AVALON_TASK"] = self.env["AVALON_TASK"] - workdir = self._S["AVALON_WORKDIR"] = self.env["AVALON_WORKDIR"] - - anatomy_filled = self.get_anatomy_filled() + # get publish version of celaction app = "celaction_publish" + + # get context variables + project = self._S["AVALON_PROJECT"] = env["AVALON_PROJECT"] + asset = self._S["AVALON_ASSET"] = env["AVALON_ASSET"] + task = self._S["AVALON_TASK"] = env["AVALON_TASK"] + workdir = self._S["AVALON_WORKDIR"] = env["AVALON_WORKDIR"] + + # get workfile path + anatomy_filled = self.get_anatomy_filled() workfile = anatomy_filled["work"]["file"] version = anatomy_filled["version"] + # create workdir if doesn't exist os.makedirs(workdir, exist_ok=True) self.log.info(f"Work dir is: `{workdir}`") - # get last version if any + # get last version of workfile workfile_last = get_last_version_from_path( workdir, workfile.split(version)) if workfile_last: workfile = workfile_last - project_file = os.path.join(workdir, workfile) - env["PYPE_CELACTION_PROJECT_FILE"] = project_file + workfile_path = os.path.join(workdir, workfile) - self.log.info(f"Workfile to open: `{project_file}`") + # create workfile from template if doesnt exist any on path + if not os.path.isfile(workfile_path): + # try to get path from environment or use default + # from `pype.celation` dir + template_path = env.get("CELACTION_TEMPLATE") or os.path.join( + env.get("PYPE_MODULE_ROOT"), + "pype/celaction/celaction_template_scene_.scn" + ) + self.log.info(f"Creating workfile from template: `{template_path}`") + shutil.copy2( + os.path.normpath(template_path), + os.path.normpath(workfile_path) + ) + + self.log.info(f"Workfile to open: `{workfile_path}`") - ########################## # setting output parameters path = r"Software\CelAction\CelAction2D\User Settings" winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) @@ -70,7 +90,7 @@ class CelactionPrelaunchHook(PypeHook): winreg.KEY_ALL_ACCESS) # TODO: change to root path and pyblish standalone to premiere way - pype_root_path = os.getenv("PYPE_ROOT") + pype_root_path = os.getenv("PYPE_SETUP_PATH") path = os.path.join(pype_root_path, "pype.bat") From d809d6e4b6671441ce720d29419a0db50d8cf510 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 3 Jun 2020 11:50:32 +0100 Subject: [PATCH 27/40] fix(celaction): missing environment var for opening workfile --- pype/hooks/celaction/prelaunch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index ffd3a78bf3..55956a7481 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -65,7 +65,7 @@ class CelactionPrelaunchHook(PypeHook): workfile_path = os.path.join(workdir, workfile) - # create workfile from template if doesnt exist any on path + # copy workfile from template if doesnt exist any on path if not os.path.isfile(workfile_path): # try to get path from environment or use default # from `pype.celation` dir @@ -81,6 +81,9 @@ class CelactionPrelaunchHook(PypeHook): self.log.info(f"Workfile to open: `{workfile_path}`") + # adding compulsory environment var for openting file + env["PYPE_CELACTION_PROJECT_FILE"] = workfile_path + # setting output parameters path = r"Software\CelAction\CelAction2D\User Settings" winreg.CreateKey(winreg.HKEY_CURRENT_USER, path) From 12dee5053be469ec968d1a8eedfedb49e3f331f9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 17:07:22 +0200 Subject: [PATCH 28/40] fix(global): i is not instance as it should --- pype/plugins/global/publish/collect_rendered_files.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index bd54204c96..e0f3695fd5 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -101,13 +101,14 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): # add audio if in metadata data if data.get("audio"): - i.data.update({ + instance.data.update({ "audio": [{ "filename": data.get("audio"), "offset": 0 }] }) - self.log.info(f"Adding audio to instance: {i.data['audio']}") + self.log.info( + f"Adding audio to instance: {instance.data['audio']}") def process(self, context): self._context = context From b663ad42e62303fe039e893fc986a44d1aff2e54 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 17:32:46 +0200 Subject: [PATCH 29/40] fix(global): convert to unc path from drive - also adding PYPE_PYTHON_EXE to submission --- .../plugins/global/publish/submit_publish_job.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index d2d42c7d15..79a02e11cf 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -14,6 +14,8 @@ import pyblish.api def _get_script(): """Get path to the image sequence script.""" + from pathlib import Path + try: from pype.scripts import publish_filesequence except Exception: @@ -23,7 +25,9 @@ def _get_script(): if module_path.endswith(".pyc"): module_path = module_path[: -len(".pyc")] + ".py" - return os.path.normpath(module_path) + path = Path(os.path.normpath(module_path)).resolve(strict=True) + + return str(path) def get_latest_version(asset_name, subset_name, family): @@ -157,7 +161,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_SERVER", "PYPE_METADATA_FILE", "AVALON_PROJECT", - "PYPE_LOG_NO_COLORS" + "PYPE_LOG_NO_COLORS", + "PYPE_PYTHON_EXE" ] # custom deadline atributes @@ -209,6 +214,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ).format(output_dir)) rootless_path = output_dir + # gets script path + script_path = _get_script() + self.log.info("Adding script path: `{}`...".format(script_path)) + # Generate the payload for Deadline submission payload = { "JobInfo": { @@ -231,7 +240,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): }, "PluginInfo": { "Version": "3.6", - "ScriptFile": _get_script(), + "ScriptFile": script_path, "Arguments": "", "SingleFrameOnly": "True", }, @@ -245,7 +254,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): metadata_path = os.path.join(rootless_path, metadata_filename) environment = job["Props"].get("Env", {}) - environment["PYPE_PYTHON_EXE"] = "//pype/Core/software/python36/python.exe" environment["PYPE_LOG_NO_COLORS"] = "1" environment["PYPE_METADATA_FILE"] = metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] From 5606277f6ee0e2253ff37e9fa60ce925ed8f32a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 17:34:07 +0200 Subject: [PATCH 30/40] clean(global) --- pype/plugins/global/publish/integrate_new.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index bd908901cc..8d7a722c5c 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -375,9 +375,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): index_frame_start += 1 dst = "{0}{1}{2}".format( - dst_head, - dst_padding, - dst_tail).replace("..", ".") + dst_head, + dst_padding, + dst_tail + ).replace("..", ".") self.log.debug("destination: `{}`".format(dst)) src = os.path.join(stagingdir, src_file_name) From 46e07c03544ea261c1d4dd3a9281b5c0e0423973 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 18:10:28 +0200 Subject: [PATCH 31/40] fix(global): if speedcopy fails copy with shutill --- pype/plugins/global/publish/integrate_new.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 8d7a722c5c..fcc4adb256 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -377,8 +377,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst = "{0}{1}{2}".format( dst_head, dst_padding, - dst_tail - ).replace("..", ".") + dst_tail).replace("..", ".") self.log.debug("destination: `{}`".format(dst)) src = os.path.join(stagingdir, src_file_name) @@ -557,10 +556,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): while True: try: copyfile(src, dst) - except OSError as e: - self.log.critical("Cannot copy {} to {}".format(src, dst)) - self.log.critical(e) - six.reraise(*sys.exc_info()) + except (OSError, AttributeError) as e: + self.log.warning(e) + # try it again with shutil + import shutil + try: + shutil.copyfile(src, dst) + except (OSError, AttributeError) as e: + self.log.critical("Cannot copy {} to {}".format(src, dst)) + self.log.critical(e) + six.reraise(*sys.exc_info()) if str(getsize(src)) in str(getsize(dst)): break From 1213357d29aadde37f4875dbccd19cc10dad2660 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 18:19:55 +0200 Subject: [PATCH 32/40] feat(global): adding debug print --- pype/plugins/global/publish/integrate_new.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index fcc4adb256..5c30a7b4bc 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -562,6 +562,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): import shutil try: shutil.copyfile(src, dst) + self.log.debug("Copying files with shutil...") except (OSError, AttributeError) as e: self.log.critical("Cannot copy {} to {}".format(src, dst)) self.log.critical(e) From 082b7cf5c560f68284bbde6c4879687119323f64 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 18:11:43 +0100 Subject: [PATCH 33/40] feat(global): adding celaction detect for preview tags and families --- pype/plugins/global/publish/extract_review.py | 7 +++++-- pype/plugins/global/publish/submit_publish_job.py | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/extract_review.py b/pype/plugins/global/publish/extract_review.py index 228b4cd6f4..6ab834d1c4 100644 --- a/pype/plugins/global/publish/extract_review.py +++ b/pype/plugins/global/publish/extract_review.py @@ -610,8 +610,8 @@ class ExtractReview(pyblish.api.InstancePlugin): # NOTE Skipped using instance's resolution full_input_path_single_file = temp_data["full_input_path_single_file"] input_data = pype.lib.ffprobe_streams(full_input_path_single_file)[0] - input_width = input_data["width"] - input_height = input_data["height"] + input_width = int(input_data["width"]) + input_height = int(input_data["height"]) self.log.debug("pixel_aspect: `{}`".format(pixel_aspect)) self.log.debug("input_width: `{}`".format(input_width)) @@ -631,6 +631,9 @@ class ExtractReview(pyblish.api.InstancePlugin): output_width = input_width output_height = input_height + output_width = int(output_width) + output_height = int(output_height) + self.log.debug( "Output resolution is {}x{}".format(output_width, output_height) ) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 79a02e11cf..e37444964a 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -490,6 +490,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if bake_render_path: preview = False + if "celaction" in self.hosts: + preview = True + staging = os.path.dirname(list(collection)[0]) success, rootless_staging_dir = ( self.anatomy.find_root_template_from_path(staging) From 1a21abdecf72e992aadec15d4afe840edec11834 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 19:24:21 +0200 Subject: [PATCH 34/40] feat(celaction): new pype structure --- pype/hooks/celaction/prelaunch.py | 2 +- pype/{ => hosts}/celaction/__init__.py | 2 +- .../celaction/celaction_template_scene.scn | Bin pype/{ => hosts}/celaction/cli.py | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename pype/{ => hosts}/celaction/__init__.py (93%) rename pype/{ => hosts}/celaction/celaction_template_scene.scn (100%) rename pype/{ => hosts}/celaction/cli.py (100%) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 55956a7481..4aa4237035 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -71,7 +71,7 @@ class CelactionPrelaunchHook(PypeHook): # from `pype.celation` dir template_path = env.get("CELACTION_TEMPLATE") or os.path.join( env.get("PYPE_MODULE_ROOT"), - "pype/celaction/celaction_template_scene_.scn" + "pype/hosts/celaction/celaction_template_scene.scn" ) self.log.info(f"Creating workfile from template: `{template_path}`") shutil.copy2( diff --git a/pype/celaction/__init__.py b/pype/hosts/celaction/__init__.py similarity index 93% rename from pype/celaction/__init__.py rename to pype/hosts/celaction/__init__.py index 47e81a9212..8c93d93738 100644 --- a/pype/celaction/__init__.py +++ b/pype/hosts/celaction/__init__.py @@ -1 +1 @@ -kwargs = None +kwargs = None diff --git a/pype/celaction/celaction_template_scene.scn b/pype/hosts/celaction/celaction_template_scene.scn similarity index 100% rename from pype/celaction/celaction_template_scene.scn rename to pype/hosts/celaction/celaction_template_scene.scn diff --git a/pype/celaction/cli.py b/pype/hosts/celaction/cli.py similarity index 100% rename from pype/celaction/cli.py rename to pype/hosts/celaction/cli.py From c01115386d5e8d950b6220e6980cce3f388d35cb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Jun 2020 19:26:57 +0200 Subject: [PATCH 35/40] clean(celaction): before PR brushing --- pype/hooks/celaction/prelaunch.py | 6 ++---- pype/hosts/celaction/cli.py | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 4aa4237035..72900fd8d8 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -29,9 +29,6 @@ class CelactionPrelaunchHook(PypeHook): self.signature = "( {} )".format(self.__class__.__name__) def execute(self, *args, env: dict = None) -> bool: - - from pprint import pformat - self.log.info(f"`{pformat(env)}`") if not env: env = os.environ @@ -73,7 +70,8 @@ class CelactionPrelaunchHook(PypeHook): env.get("PYPE_MODULE_ROOT"), "pype/hosts/celaction/celaction_template_scene.scn" ) - self.log.info(f"Creating workfile from template: `{template_path}`") + self.log.info( + f"Creating workfile from template: `{template_path}`") shutil.copy2( os.path.normpath(template_path), os.path.normpath(workfile_path) diff --git a/pype/hosts/celaction/cli.py b/pype/hosts/celaction/cli.py index daa971d8d5..6b0c2eeafa 100644 --- a/pype/hosts/celaction/cli.py +++ b/pype/hosts/celaction/cli.py @@ -2,10 +2,8 @@ import os import sys import copy import argparse -import importlib from avalon import io -import avalon.api from avalon.tools import publish import pyblish.api @@ -53,7 +51,7 @@ def cli(): help=("Height of resolution")) # parser.add_argument("--programDir", - # help=("Directory with celaction program installation")) + # help=("Directory with celaction program installation")) pype.celaction.kwargs = parser.parse_args(sys.argv[1:]).__dict__ From b8dde529b24c2bf0ecc8c9eca090c04128438065 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 9 Jun 2020 08:32:04 +0200 Subject: [PATCH 36/40] fix(celaction, global): PR comments --- pype/hooks/celaction/prelaunch.py | 7 +- pype/hosts/celaction/cli.py | 10 +- .../append_celaction_ftrack_asset_name.py | 19 -- .../append_celaction_ftrack_data.py | 46 --- .../collect_celaction_scene.py | 10 - .../celaction/_unused_publish/collect_data.py | 18 -- .../_unused_publish/collect_render_path.py | 60 ---- .../extract_celaction_deadline_old.py | 109 ------- .../_unused_publish/submit_publish_job.py | 283 ------------------ .../validate_celaction_scene_path.py | 62 ---- .../celaction/publish/collect_audio.py | 1 - .../celaction/publish/collect_render_path.py | 4 +- .../publish/submit_celaction_deadline.py | 20 -- pype/plugins/global/publish/integrate_new.py | 15 +- .../global/publish/submit_publish_job.py | 2 +- 15 files changed, 16 insertions(+), 650 deletions(-) delete mode 100644 pype/plugins/celaction/_unused_publish/append_celaction_ftrack_asset_name.py delete mode 100644 pype/plugins/celaction/_unused_publish/append_celaction_ftrack_data.py delete mode 100644 pype/plugins/celaction/_unused_publish/collect_celaction_scene.py delete mode 100644 pype/plugins/celaction/_unused_publish/collect_data.py delete mode 100644 pype/plugins/celaction/_unused_publish/collect_render_path.py delete mode 100644 pype/plugins/celaction/_unused_publish/extract_celaction_deadline_old.py delete mode 100644 pype/plugins/celaction/_unused_publish/submit_publish_job.py delete mode 100644 pype/plugins/celaction/_unused_publish/validate_celaction_scene_path.py diff --git a/pype/hooks/celaction/prelaunch.py b/pype/hooks/celaction/prelaunch.py index 72900fd8d8..df9da6cbbf 100644 --- a/pype/hooks/celaction/prelaunch.py +++ b/pype/hooks/celaction/prelaunch.py @@ -3,8 +3,11 @@ import os import winreg import shutil from pype.lib import PypeHook -from pype.api import get_last_version_from_path -from pypeapp import Anatomy, Logger +from pype.api import ( + Anatomy, + Logger, + get_last_version_from_path +) from avalon import io, api, lib diff --git a/pype/hosts/celaction/cli.py b/pype/hosts/celaction/cli.py index 6b0c2eeafa..fa55db3200 100644 --- a/pype/hosts/celaction/cli.py +++ b/pype/hosts/celaction/cli.py @@ -9,23 +9,19 @@ from avalon.tools import publish import pyblish.api import pyblish.util -from pypeapp import Logger +from pype.api import Logger import pype import pype.celaction - log = Logger().get_logger("Celaction_cli_publisher") publish_host = "celaction" -CURRENT_DIR = os.path.dirname(__file__) -PACKAGE_DIR = os.path.dirname(CURRENT_DIR) -PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, publish_host, "publish") +PUBLISH_PATH = os.path.join(pype.PLUGINS_DIR, publish_host, "publish") PUBLISH_PATHS = [ PUBLISH_PATH, - os.path.join(PLUGINS_DIR, "ftrack", "publish") + os.path.join(pype.PLUGINS_DIR, "ftrack", "publish") ] diff --git a/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_asset_name.py b/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_asset_name.py deleted file mode 100644 index 175a19edd3..0000000000 --- a/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_asset_name.py +++ /dev/null @@ -1,19 +0,0 @@ -import pyblish.api - - -class AppendCelactionFtrackAssetName(pyblish.api.InstancePlugin): - """ Appending "ftrackAssetName" """ - - label = "Ftrack Asset Name" - order = pyblish.api.CollectorOrder + 0.1 - - def process(self, instance): - - # skipping if not launched from ftrack - if "ftrackData" not in instance.context.data: - return - - ftrack_data = instance.context.data["ftrackData"] - - asset_name = ftrack_data["Task"]["name"] - instance.data["ftrackAssetName"] = asset_name diff --git a/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_data.py b/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_data.py deleted file mode 100644 index 552f4ffb1a..0000000000 --- a/pype/plugins/celaction/_unused_publish/append_celaction_ftrack_data.py +++ /dev/null @@ -1,46 +0,0 @@ -import pyblish.api -from bait.ftrack.query_runner import QueryRunner - - -class AppendCelactionFtrackAudio(pyblish.api.ContextPlugin): - - label = "Ftrack Audio" - order = pyblish.api.ExtractorOrder - - def process(self, context): - - if context.data.get("audio", ''): - self.log.info('Audio data are already collected') - self.log.info('Audio: {}'.format(context.data.get("audio", ''))) - return - - runner = QueryRunner(context.data['ftrackSession']) - - audio_file = runner.get_audio_file_for_shot( - context.data['ftrackData']["Shot"]["id"]) - - if audio_file: - context.data["audio"] = { - 'filename': audio_file, - 'enabled': True - } - else: - self.log.warning("Couldn't find any audio file on Ftrack.") - - -class AppendCelactionFtrackData(pyblish.api.InstancePlugin): - """ Appending ftrack component and asset type data """ - - families = ["img.*", "mov.*"] - # offset to piggy back from default collectors - order = pyblish.api.CollectorOrder + 0.1 - - def process(self, instance): - - # ftrack data - if not instance.context.has_data("ftrackData"): - return - - instance.data["ftrackComponents"] = {} - asset_type = instance.data["family"].split(".")[0] - instance.data["ftrackAssetType"] = asset_type diff --git a/pype/plugins/celaction/_unused_publish/collect_celaction_scene.py b/pype/plugins/celaction/_unused_publish/collect_celaction_scene.py deleted file mode 100644 index 006edcb1c3..0000000000 --- a/pype/plugins/celaction/_unused_publish/collect_celaction_scene.py +++ /dev/null @@ -1,10 +0,0 @@ -import pyblish.api - - -class CollectCelactionScene(pyblish.api.ContextPlugin): - """ Converts the path flag value to the current file in the context. """ - - order = pyblish.api.CollectorOrder - - def process(self, context): - context.data['ftrackStatus'] = "Ready" diff --git a/pype/plugins/celaction/_unused_publish/collect_data.py b/pype/plugins/celaction/_unused_publish/collect_data.py deleted file mode 100644 index ce416403cc..0000000000 --- a/pype/plugins/celaction/_unused_publish/collect_data.py +++ /dev/null @@ -1,18 +0,0 @@ -import os -import pyblish.api -import pype.celaction - - -class CollectData(pyblish.api.Collector): - """Collects data passed from via CLI""" - - order = pyblish.api.Collector.order - 0.1 - - def process(self, context): - self.log.info("Adding data from command-line into Context..") - - kwargs = pype.celaction.kwargs.copy() - - for key, value in kwargs.items(): - self.log.info("%s = %s" % (key, value)) - context.set_data(key, value) diff --git a/pype/plugins/celaction/_unused_publish/collect_render_path.py b/pype/plugins/celaction/_unused_publish/collect_render_path.py deleted file mode 100644 index 72adf57e86..0000000000 --- a/pype/plugins/celaction/_unused_publish/collect_render_path.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Requires: - context -> anatomy - context -> anatomyData - -Provides: - instance -> publishDir - instance -> resourcesDir -""" - -import os -import copy - -import pyblish.api -from avalon import api - - -class CollectRenderPath(pyblish.api.InstancePlugin): - """Generate file and directory path where rendered images will be""" - - label = "Collect Render Path" - order = pyblish.api.CollectorOrder + 0.495 - - def process(self, instance): - anatomy = instance.context.data["anatomy"] - - template_data = copy.deepcopy(instance.data["anatomyData"]) - - # This is for cases of Deprecated anatomy without `folder` - # TODO remove when all clients have solved this issue - template_data.update({ - "frame": "FRAME_TEMP", - "representation": "png" - }) - - anatomy_filled = anatomy.format(template_data) - - if "folder" in anatomy.templates["render"]: - render_folder = anatomy_filled["render"]["folder"] - render_file = anatomy_filled["render"]["file"] - else: - # solve deprecated situation when `folder` key is not underneath - # `publish` anatomy - project_name = api.Session["AVALON_PROJECT"] - self.log.warning(( - "Deprecation warning: Anatomy does not have set `folder`" - " key underneath `publish` (in global of for project `{}`)." - ).format(project_name)) - - file_path = anatomy_filled["render"]["path"] - # Directory - render_folder = os.path.dirname(file_path) - render_file = os.path.basename(file_path) - - render_folder = os.path.normpath(render_folder) - render_path = os.path.join(render_folder, render_file) - - instance.data["outputRenderPath"] = render_path - - self.log.debug("outputRenderPath: \"{}\"".format(render_path)) diff --git a/pype/plugins/celaction/_unused_publish/extract_celaction_deadline_old.py b/pype/plugins/celaction/_unused_publish/extract_celaction_deadline_old.py deleted file mode 100644 index 322bb468f9..0000000000 --- a/pype/plugins/celaction/_unused_publish/extract_celaction_deadline_old.py +++ /dev/null @@ -1,109 +0,0 @@ -import os - -import pyblish.api -import pyblish_standalone -import clique -import requests -from bait.deadline import get_render_settings, get_deadline_data, format_frames -from bait.paths import get_output_path - - -class ExtractCelactionDeadline(pyblish.api. InstancePlugin): - - label = 'Deadline' - families = ['render'] - order = pyblish.api.ExtractorOrder - - def process(self, instance): - - render_settings = get_render_settings("celaction") - - existing_data = instance.data.get( - "deadlineData", {"job": {}, "plugin": {}} - ) - - task_id = instance.context.data["ftrackData"]["Task"]["id"] - - data = get_deadline_data(render_settings, existing_data) - - filename = os.path.basename(instance.context.data["currentFile"]) - filename_no_ext, ext = os.path.splitext(filename) - - data["job"]["Name"] = filename_no_ext + " - " + instance.data["name"] - data["job"]['Frames'] = format_frames( - instance.data('start'), instance.data('end')) - - # get version data - version = instance.context.data( - 'version') if instance.context.has_data('version') else 1 - - output_path = get_output_path( - task_id, instance.data["name"], version, "png") - output_path = output_path.replace("/", "\\") - - data['job']['Plugin'] = 'CelAction' - data['job']["BatchName"] = filename - data['job']["UserName"] = os.environ['USERNAME'] - data["job"]['OutputFilename0'] = output_path.replace('%04d', '####') - - scene_path = pyblish_standalone.kwargs['path'][0] - scene_path = scene_path.replace("/", "\\") - _, ext = os.path.splitext(scene_path) - - # plugin data - self.log.info(scene_path) - - args = '{}'.format(scene_path) - args += ' -a' - args += ' -8' - args += ' -s ' - args += ' -e ' - args += ' -d {}'.format(os.path.dirname(output_path)) - args += ' -x {}'.format(instance.data('x')) - args += ' -y {}'.format(instance.data('y')) - args += ' -r {}'.format(output_path.replace('.%04d', '')) - args += ' -= AbsoluteFrameNumber=on -= PadDigits=4' - args += ' -= ClearAttachment=on' - - data["plugin"]['StartupDirectory'] = '' - data["plugin"]['Arguments'] = args - - self.log.info(data) - - head = output_path.replace('%04d', '') - tail = ".png" - collection = clique.Collection(head=head, padding=4, tail=tail) - - frame_start = int(instance.data['start']) - frame_end = int(instance.data['end']) - - for frame_no in range(frame_start, frame_end): - collection.add(head + str(frame_no).zfill(4) + tail) - - # instance.data["collection"] = collection - - # adding to instance - instance.set_data('deadlineData2', value=data) - # instance.set_data('deadlineSubmissionJob', value=data) - - payload = { - "JobInfo": data["job"], - "PluginInfo": data["plugin"], - "AuxFiles": [] - } - - url = "{}/api/jobs".format('http://192.168.146.8:8082') - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - - # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname( - data["job"]['OutputFilename0']) - instance.data["deadlineSubmissionJob"] = response.json() - - instance.context.data['ftrackStatus'] = "Render" - - # creating output path - if not os.path.exists(os.path.dirname(output_path)): - os.makedirs(os.path.dirname(output_path)) diff --git a/pype/plugins/celaction/_unused_publish/submit_publish_job.py b/pype/plugins/celaction/_unused_publish/submit_publish_job.py deleted file mode 100644 index 2767378b4a..0000000000 --- a/pype/plugins/celaction/_unused_publish/submit_publish_job.py +++ /dev/null @@ -1,283 +0,0 @@ -import os -import json -import pprint -import re -import requests -import pyblish.api - - -class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): - """Submit image sequence publish jobs to Deadline. - - These jobs are dependent on a deadline job submission prior to this - plug-in. - - Renders are submitted to a Deadline Web Service as - supplied via the environment variable DEADLINE_REST_URL - - Options in instance.data: - - deadlineSubmission (dict, Required): The returned .json - data from the job submission to deadline. - - - outputDir (str, Required): The output directory where the metadata - file should be generated. It's assumed that this will also be - final folder containing the output files. - - - ext (str, Optional): The extension (including `.`) that is required - in the output filename to be picked up for image sequence - publishing. - - - publishJobState (str, Optional): "Active" or "Suspended" - This defaults to "Suspended" - - This requires a "startFrame" and "endFrame" to be present in instance.data - or in context.data. - - """ - - label = "Submit image sequence jobs to Deadline" - order = pyblish.api.IntegratorOrder + 0.2 - - hosts = ["celaction"] - - families = [ - "render", - "deadline" - ] - - def process(self, instance): - - # DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL", - # "http://localhost:8082") - # assert DEADLINE_REST_URL, "Requires DEADLINE_REST_URL" - - # Get a submission job - - job = instance.data.get("deadlineSubmissionJob") - if not job: - raise RuntimeError("Can't continue without valid deadline " - "submission prior to this plug-in.") - ################ - ft_data = instance.context.data["ftrackData"] - project = ft_data['Project']['name'] - project_code = ft_data['Project']['code'] - projects_path = os.path.dirname(ft_data['Project']['root']) - - data = instance.data.copy() - asset = instance.context.data["ftrackData"]['Shot']['name'] - subset = 'render' + \ - instance.context.data["ftrackData"]['Task']['name'].capitalize() - - state = data.get("publishJobState", "Suspended") - # job_name = "{batch} - {subset} [publish image sequence]".format( - # batch=job["Props"]["Name"], - # subset=subset - # ) - job_name = "{asset} [publish image sequence]".format( - asset=asset - ) - - # Get start/end frame from instance, if not available get from context - context = instance.context - - start = int(instance.data['start']) - end = int(instance.data['end']) - - try: - source = data['source'] - except KeyError: - source = context.data["currentFile"] - - # Write metadata for publish job - render_job = data.pop("deadlineSubmissionJob") - metadata = { - "asset": asset, - "regex": r"^.*\.png", - "subset": subset, - "startFrame": start, - "endFrame": end, - "fps": context.data.get("fps", None), - "families": ["render"], - "source": source, - "user": context.data["user"], - "version": context.data.get('version'), - "audio": context.data["audio"]['filename'], - # Optional metadata (for debugging) - "metadata": { - "instance": data, - "job": job, - "session": fake_avalon_session(project, projects_path) - } - } - - # Ensure output dir exists - output_dir = instance.data["outputDir"] - - if not os.path.isdir(output_dir): - os.makedirs(output_dir) - - for k, v in metadata.items(): - self.log.info(k) - self.log.info(v) - - metadata_filename = "{}_metadata.json".format(subset) - metadata_path = os.path.join(output_dir, metadata_filename) - with open(metadata_path, "w") as f: - json.dump(metadata, f, indent=4, sort_keys=True) - - # Generate the payload for Deadline submission - payload = { - "JobInfo": { - "Plugin": "Python", - "BatchName": job["Props"]["Batch"], - "Name": job_name, - "JobType": "Normal", - "Group": "celaction", - "JobDependency0": job["_id"], - "UserName": os.environ['USERNAME'], - "Comment": instance.context.data.get("comment", ""), - "InitialStatus": "Active" - }, - "PluginInfo": { - "Version": "3.6", - "ScriptFile": r"\\pype\Core\dev\pype-setup\repos\pype-config\pype\scripts\publish_filesequence.py", - "Arguments": '--path "{}"'.format(metadata_path), - "SingleFrameOnly": "True" - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - # Transfer the environment from the original job to this dependent - # job so they use the same environment - environment = fake_env() - environment["AVALON_ASSET"] = asset - environment["AVALON_TASK"] = instance.context.data["ftrackData"]['Task']['name'] - environment["AVALON_PROJECT"] = project - environment["AVALON_PROJECTS"] = projects_path - environment["PYPE_STUDIO_PROJECTS_PUBLISH"] = ft_data['Project']['root'] - environment["PYPE_STUDIO_PROJECTS_RENDER"] = ft_data['Project']['root'] - environment["PYPE_STUDIO_PROJECTS_RESOURCES"] = ft_data['Project']['root'] - environment["PYPE_STUDIO_PROJECTS_WORK"] = ft_data['Project']['root'] - - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - - # Avoid copied pools and remove secondary pool - payload["JobInfo"]["Pool"] = "animation_2d" - payload["JobInfo"].pop("SecondaryPool", None) - - self.log.info("Submitting..") - # self.log.info(json.dumps(payload, indent=4, sort_keys=True)) - - ################ - ###################### - fake_instance = instance.context.create_instance( - name=(str(instance) + "1")) - - for k, v in data.items(): - self.log.info(k) - fake_instance.data[k] = v - - # fake_instance.data['deadlineData'] = payload - # 'http://192.168.146.8:8082' - url = "{}/api/jobs".format('http://192.168.146.8:8082') - response = requests.post(url, json=payload) - if not response.ok: - raise Exception(response.text) - ###################### - ####################### - - -def fake_avalon_session(project=None, projects_path=None): - return { - "AVALON_APP": "premiere", - "AVALON_APP_VERSION": "2019", - "AVALON_ASSET": "editorial", - "AVALON_CONFIG": "pype", - "AVALON_CONTAINER_ID": "avalon.container", - "AVALON_DB": "Pype", - "AVALON_DEADLINE": "http://192.168.146.8:8082", - "AVALON_DEBUG": "1", - "AVALON_EARLY_ADOPTER": "1", - "AVALON_INSTANCE_ID": "avalon.instance", - "AVALON_LABEL": "Avalon", - "AVALON_LOCATION": "http://127.0.0.1", - "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", - "AVALON_PASSWORD": "secret", - "AVALON_PROJECT": project or "LBB2_dev", - "AVALON_PROJECTS": projects_path or "L:/PYPE_test", - "AVALON_SILO": "editorial", - "AVALON_TASK": "conform", - "AVALON_TIMEOUT": "1000", - "AVALON_USERNAME": "avalon", - "AVALON_WORKDIR": "L:/PYPE_test/episodes/editorial/work/conform", - "schema": "avalon-core:session-1.0" - } - - -def fake_env(): - return { - "AVALON_CONFIG": "pype", - "AVALON_CONTAINER_ID": "avalon.container", - "AVALON_CORE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core", - "AVALON_DB": "Pype", - "AVALON_DB_DATA": "\\\\pype\\Core\\dev\\mongo_db_data", - "AVALON_DEADLINE": "http://192.168.146.8:8082", - "AVALON_DEBUG": "1", - "AVALON_EARLY_ADOPTER": "1", - "AVALON_ENV_NAME": "pype_env", - "AVALON_HIERARCHY": "", - "AVALON_INSTANCE_ID": "avalon.instance", - "AVALON_LABEL": "Avalon", - "AVALON_LAUNCHER": "\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher", - "AVALON_LOCATION": "http://127.0.0.1", - "AVALON_MONGO": "mongodb://PypeAdmin:X34vkuwL4wbK9A7X@192.168.146.24:27072/Pype", - "AVALON_MONGO_PORT": "27072", - "AVALON_PASSWORD": "secret", - "AVALON_SCHEMA": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\schema", - "AVALON_SILO": "", - "AVALON_TIMEOUT": "1000", - "AVALON_USERNAME": "avalon", - "AVALON_WORKDIR": "default", - "DEADLINE_PATH": "C:\\Program Files\\Thinkbox\\Deadline10\\bin", - "DEADLINE_REST_URL": "http://192.168.146.8:8082", - "FTRACK_API_KEY": "NGI0ZGU3ZjMtNzNiZC00NGVlLWEwY2EtMzA1OWJlZGM0MjAyOjozZWZmMThjZi04MjkwLTQxMzQtODUwMC03NTZhMGJiZTM2MTA", - "FTRACK_API_USER": "license@clothcatanimation.com", - "FTRACK_SERVER": "https://clothcat2.ftrackapp.com", - "MONGO_DB_ENTRYDB": "Pype", - "MONGO_DB_PASS": "X34vkuwL4wbK9A7X", - "MONGO_DB_USER": "PypeAdmin", - "PATH": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\lib\\site-packages\\PyQt5\\Qt\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Scripts;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library\\bin;\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env\\python3\\Library;;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin;\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\bin\\windows;\\\\pype\\Core\\dev\\pype-setup\\app;\\\\pype\\core\\software\\ffmpeg\\bin;\\\\pype\\Core\\dev\\Applications\\djv\\bin", - "PYBLISHPLUGINPATH": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\plugins\\ftrack\\publish;", - "PYBLISH_BASE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base", - "PYBLISH_HOSTS": "shell", - "PYBLISH_LITE": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite", - "PYBLISH_QML": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml", - "PYPE_APP_ROOT": "\\\\pype\\Core\\dev\\pype-setup\\app", - "PYPE_DEBUG": "3", - "PYPE_DEBUG_STDOUT": "0", - "PYPE_SETUP_ROOT": "\\\\pype\\Core\\dev\\pype-setup", - "PYPE_STUDIO_CODE": "CC", - "PYPE_STUDIO_CONFIG": "\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config", - "PYPE_STUDIO_CORE": "\\\\pype\\Core\\dev", - "PYPE_STUDIO_CORE_MOUNT": "\\\\pype\\Core\\dev", - "PYPE_STUDIO_NAME": "Cloth Cat", - "PYPE_STUDIO_SOFTWARE": "\\\\pype\\Core\\dev\\Applications", - "PYPE_STUDIO_TEMPLATES": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates", - "PYPE_STUDIO_TOOLS": "\\\\pype\\Core\\dev\\production\\tools", - "PYTHONPATH": "\\\\pype\\Core\\dev\\pype-setup;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-core;\\\\pype\\Core\\dev\\pype-setup\\repos\\avalon-launcher;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-base;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-qml;\\\\pype\\Core\\dev\\pype-setup\\repos\\pyblish-lite;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config;\\\\pype\\Core\\dev\\pype-setup\\app\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\pype-config\\pype\\vendor;\\\\pype\\Core\\dev\\pype-setup\\repos\\ftrack-event-server", - "PYTHONVERBOSE": "True", - "PYTHON_ENV": "C:\\Users\\Public\\pype_env", - "REMOTE_ENV_DIR": "\\\\pype\\Core\\dev\\pype-setup\\bin\\python\\pype_env", - "REMOTE_ENV_ON": "0", - "SCHEMA": "avalon-core:session-1.0", - "STUDIO_SOFT": "\\\\evo2\\core\\Applications", - "TOOL_ENV": "\\\\pype\\Core\\dev\\pype-setup\\repos\\clothcat-templates\\environments", - "USERNAME": "pype" - } diff --git a/pype/plugins/celaction/_unused_publish/validate_celaction_scene_path.py b/pype/plugins/celaction/_unused_publish/validate_celaction_scene_path.py deleted file mode 100644 index 17365178ac..0000000000 --- a/pype/plugins/celaction/_unused_publish/validate_celaction_scene_path.py +++ /dev/null @@ -1,62 +0,0 @@ -import shutil -import pyblish.api -import pyblish_standalone -import os -from bait.paths import get_env_work_file - - -class RepairCelactionScenePath(pyblish.api.Action): - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - - # get version data - version = context.data('version') if context.has_data('version') else 1 - - task_id = context.data["ftrackData"]["Task"]["id"] - expected_path = get_env_work_file( - "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') - - src = context.data["currentFile"] - - if not os.path.exists(os.path.dirname(expected_path)): - os.makedirs(os.path.dirname(expected_path)) - - if os.path.exists(os.path.dirname(expected_path)): - self.log.info("existing to \"%s\"" % expected_path) - - if os.path.exists(expected_path) and ('v001' in expected_path): - os.remove(expected_path) - - shutil.copy2(src, expected_path) - - pyblish_standalone.kwargs['path'] = [expected_path] - context.data["currentFile"] = expected_path - - self.log.info("Saved to \"%s\"" % expected_path) - - -class ValidateCelactionScenePath(pyblish.api.InstancePlugin): - order = pyblish.api.ValidatorOrder - families = ['scene'] - label = 'Scene Path' - actions = [RepairCelactionScenePath] - - def process(self, instance): - - # getting current work file - current_scene_path = pyblish_standalone.kwargs['path'][0] - - version = instance.context.data( - 'version') if instance.context.has_data('version') else 1 - - task_id = instance.context.data["ftrackData"]["Task"]["id"] - expected_scene_path = get_env_work_file( - "celaction", task_id, "scn", version).replace('\\\\\\', '\\\\') - - msg = 'Scene path is not correct: Current: {}, Expected: {}'.format( - current_scene_path, expected_scene_path) - - assert expected_scene_path == current_scene_path, msg diff --git a/pype/plugins/celaction/publish/collect_audio.py b/pype/plugins/celaction/publish/collect_audio.py index 44b40f046c..89fe6bf966 100644 --- a/pype/plugins/celaction/publish/collect_audio.py +++ b/pype/plugins/celaction/publish/collect_audio.py @@ -2,7 +2,6 @@ import pyblish.api import os import pype.api as pype -from avalon import io from pprint import pformat class AppendCelactionAudio(pyblish.api.ContextPlugin): diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py index 32a4bf7c32..3d092ccf22 100644 --- a/pype/plugins/celaction/publish/collect_render_path.py +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -13,13 +13,13 @@ class CollectRenderPath(pyblish.api.InstancePlugin): anatomy = instance.context.data["anatomy"] current_file = instance.context.data["currentFile"] work_dir = os.path.dirname(current_file) - work_file = os.path.basename(current_file) padding = anatomy.templates.get("frame_padding", 4) render_dir = os.path.join( work_dir, "render", "celaction" ) render_path = os.path.join( - render_dir, ".".join([instance.data["subset"], f"%0{padding}d", "png"]) + render_dir, + ".".join([instance.data["subset"], f"%0{padding}d", "png"]) ) # create dir if it doesnt exists diff --git a/pype/plugins/celaction/publish/submit_celaction_deadline.py b/pype/plugins/celaction/publish/submit_celaction_deadline.py index 7b4366ca24..0bb346f7cf 100644 --- a/pype/plugins/celaction/publish/submit_celaction_deadline.py +++ b/pype/plugins/celaction/publish/submit_celaction_deadline.py @@ -2,7 +2,6 @@ import os import json import getpass -from avalon import api from avalon.vendor import requests import re import pyblish.api @@ -29,8 +28,6 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): deadline_chunk_size = 1 def process(self, instance): - families = instance.data["families"] - context = instance.context DEADLINE_REST_URL = os.environ.get("DEADLINE_REST_URL") @@ -155,23 +152,6 @@ class ExtractCelactionDeadline(pyblish.api.InstancePlugin): "AuxFiles": [] } - # Include critical environment variables with submission - keys = [ - "FTRACK_API_USER", - "FTRACK_API_KEY", - "FTRACK_SERVER", - "AVALON_PROJECT" - ] - environment = dict({key: os.environ[key] for key in keys - if key in os.environ}) - - payload["JobInfo"].update({ - "EnvironmentKeyValue%d" % index: "{key}={value}".format( - key=key, - value=environment[key] - ) for index, key in enumerate(environment) - }) - plugin = payload["JobInfo"]["Plugin"] self.log.info("using render plugin : {}".format(plugin)) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 16785178e1..6c9caf7ab9 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -9,7 +9,7 @@ import six from pymongo import DeleteOne, InsertOne import pyblish.api -from avalon import io +from avalon import api, io from avalon.vendor import filelink # this is needed until speedcopy for linux is fixed @@ -44,7 +44,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "frameStart" "frameEnd" 'fps' - "data": additional metadata for each representation. """ label = "Integrate Asset New" @@ -82,8 +81,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "assembly", "fbx", "textures", - "action", - "harmony.template" + "action" ] exclude_families = ["clip"] db_representation_context_keys = [ @@ -379,8 +377,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst = "{0}{1}{2}".format( dst_head, dst_padding, - dst_tail - ).replace("..", ".") + dst_tail).replace("..", ".") self.log.debug("destination: `{}`".format(dst)) src = os.path.join(stagingdir, src_file_name) @@ -453,15 +450,13 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre_id is None: repre_id = io.ObjectId() - data = repre.get("data") or {} - data.update({'path': dst, 'template': template}) representation = { "_id": repre_id, "schema": "pype:representation-2.0", "type": "representation", "parent": version_id, "name": repre['name'], - "data": data, + "data": {'path': dst, 'template': template}, "dependencies": instance.data.get("dependencies", "").split(), # Imprint shortcut to context @@ -568,7 +563,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): try: shutil.copyfile(src, dst) self.log.debug("Copying files with shutil...") - except (OSError, AttributeError) as e: + except (OSError) as e: self.log.critical("Cannot copy {} to {}".format(src, dst)) self.log.critical(e) six.reraise(*sys.exc_info()) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index e37444964a..6da459690d 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -845,7 +845,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # add audio to metadata file if available audio_file = context.data.get("audioFile") if os.path.isfile(audio_file): - publish_job.update({ "audio": audio_file}) + publish_job.update({"audio": audio_file}) # pass Ftrack credentials in case of Muster if submission_type == "muster": From 43730acc3974c04da6ef05ff46c55bfe039e407c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 9 Jun 2020 08:36:30 +0200 Subject: [PATCH 37/40] fix(global): Hound suggestions --- pype/plugins/global/publish/integrate_new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 6c9caf7ab9..2bb7ba448e 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -9,7 +9,7 @@ import six from pymongo import DeleteOne, InsertOne import pyblish.api -from avalon import api, io +from avalon import io from avalon.vendor import filelink # this is needed until speedcopy for linux is fixed From 192e983649bf228860cf339414dd95c9fbff53cc Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 10 Jun 2020 18:15:39 +0200 Subject: [PATCH 38/40] Update integrate_new.py return removed family --- pype/plugins/global/publish/integrate_new.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 2bb7ba448e..82d60ae5ae 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -81,7 +81,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "assembly", "fbx", "textures", - "action" + "action", + "harmony.template" ] exclude_families = ["clip"] db_representation_context_keys = [ From e99ab29c6c43f7ada8dd865888c92e74c50e6c45 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 Jun 2020 12:17:06 +0300 Subject: [PATCH 39/40] fix(celaction): PR suggested changes --- pype/lib.py | 13 ++++++++----- pype/plugins/celaction/publish/collect_audio.py | 2 +- .../publish/collect_celaction_instances.py | 5 +---- pype/plugins/global/publish/submit_publish_job.py | 7 +------ 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index 092079ccb3..7c7a01d5cc 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -469,7 +469,7 @@ def get_version_from_path(file): ) -def get_last_version_from_path(path_dir, filter=None): +def get_last_version_from_path(path_dir, filter): """ Finds last version of given directory content @@ -480,16 +480,19 @@ def get_last_version_from_path(path_dir, filter=None): Returns: string: file name with last version + Example: + last_version_file = get_last_version_from_path( + "/project/shots/shot01/work", ["shot01", "compositing", "nk"]) """ + assert os.path.isdir(path_dir), "`path_dir` argument needs to be directory" + assert isinstance(filter, list) and ( + len(filter) != 0), "`filter` argument needs to be list and not empty" filtred_files = list() # form regex for filtering - patern = r".*" - - if filter: - patern = patern.join(filter) + patern = r".*".join(filter) for f in os.listdir(path_dir): if not re.findall(patern, f): diff --git a/pype/plugins/celaction/publish/collect_audio.py b/pype/plugins/celaction/publish/collect_audio.py index 89fe6bf966..610b81d056 100644 --- a/pype/plugins/celaction/publish/collect_audio.py +++ b/pype/plugins/celaction/publish/collect_audio.py @@ -4,6 +4,7 @@ import os import pype.api as pype from pprint import pformat + class AppendCelactionAudio(pyblish.api.ContextPlugin): label = "Colect Audio for publishing" @@ -12,7 +13,6 @@ class AppendCelactionAudio(pyblish.api.ContextPlugin): def process(self, context): self.log.info('Collecting Audio Data') asset_entity = context.data["assetEntity"] - asset_id = asset_entity["_id"] # get all available representations subsets = pype.get_subsets(asset_entity["name"], diff --git a/pype/plugins/celaction/publish/collect_celaction_instances.py b/pype/plugins/celaction/publish/collect_celaction_instances.py index 583cb7514b..aa2bb5da5d 100644 --- a/pype/plugins/celaction/publish/collect_celaction_instances.py +++ b/pype/plugins/celaction/publish/collect_celaction_instances.py @@ -1,7 +1,7 @@ import os from avalon import api import pyblish.api -from pype import api as pype + class CollectCelactionInstances(pyblish.api.ContextPlugin): """ Adds the celaction render instances """ @@ -36,7 +36,6 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): if celaction_kwargs: shared_instance_data.update(celaction_kwargs) - # ___________________________________________ # workfile instance family = "workfile" subset = family + task.capitalize() @@ -67,7 +66,6 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): self.log.info('Publishing Celaction workfile') - # ___________________________________________ # render instance family = "render.farm" subset = f"render{task}Main" @@ -89,6 +87,5 @@ class CollectCelactionInstances(pyblish.api.ContextPlugin): self.log.info('Publishing Celaction render instance') self.log.debug(f"Instance data: `{instance.data}`") - for i in context: self.log.debug(f"{i.data['families']}") diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 6da459690d..64adf74fe8 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -214,10 +214,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ).format(output_dir)) rootless_path = output_dir - # gets script path - script_path = _get_script() - self.log.info("Adding script path: `{}`...".format(script_path)) - # Generate the payload for Deadline submission payload = { "JobInfo": { @@ -240,7 +236,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): }, "PluginInfo": { "Version": "3.6", - "ScriptFile": script_path, + "ScriptFile": _get_script(), "Arguments": "", "SingleFrameOnly": "True", }, @@ -254,7 +250,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): metadata_path = os.path.join(rootless_path, metadata_filename) environment = job["Props"].get("Env", {}) - environment["PYPE_LOG_NO_COLORS"] = "1" environment["PYPE_METADATA_FILE"] = metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] environment["PYPE_LOG_NO_COLORS"] = "1" From 81219ff4b7a1299a7ab9318d3d082d5efe629bcb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 12 Jun 2020 12:39:05 +0300 Subject: [PATCH 40/40] fix(celaction): Hound suggestion --- pype/plugins/celaction/publish/collect_render_path.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/celaction/publish/collect_render_path.py b/pype/plugins/celaction/publish/collect_render_path.py index 3d092ccf22..cddd2643d8 100644 --- a/pype/plugins/celaction/publish/collect_render_path.py +++ b/pype/plugins/celaction/publish/collect_render_path.py @@ -2,7 +2,6 @@ import os import pyblish.api - class CollectRenderPath(pyblish.api.InstancePlugin): """Generate file and directory path where rendered images will be"""