From 87e60dc5f50eec7bcec6153c70125a3343832b12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 15:44:43 +0100 Subject: [PATCH 01/38] added function which returns arguments for pype subprocess --- pype/lib/__init__.py | 2 ++ pype/lib/execute.py | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index d10f3d199d..59eb2a645c 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -14,6 +14,7 @@ site.addsitedir( from .terminal import Terminal from .execute import ( + get_pype_execute_args, execute, run_subprocess ) @@ -112,6 +113,7 @@ from .editorial import ( terminal = Terminal __all__ = [ + "get_pype_execute_args", "execute", "run_subprocess", diff --git a/pype/lib/execute.py b/pype/lib/execute.py index 1f1adcdf23..6ad43c5e0d 100644 --- a/pype/lib/execute.py +++ b/pype/lib/execute.py @@ -133,3 +133,26 @@ def run_subprocess(*args, **kwargs): raise RuntimeError(exc_msg) return full_output + + +def get_pype_execute_args(): + """Arguments to run pype command. + + Arguments for subprocess when need to spawn new pype process. Which may be + needed when new python process for pype scripts must be executed in build + pype. + + ## Why is this needed? + Pype executed from code has different executable set to virtual env python + and must have path to script as first argument which is not needed for + build pype. + """ + pype_executable = os.environ["PYPE_EXECUTABLE"] + args = [pype_executable] + + executable_filename = os.path.dirname(pype_executable) + if "python" in executable_filename.lower(): + args.append( + os.path.join(os.environ["PYPE_ROOT"], "start.py") + ) + return args From 04af95ac0195cf78051c51fe29a4ab0f7a03ca46 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 15:52:46 +0100 Subject: [PATCH 02/38] standalone publisher and ftrack server are using `get_pype_execute_args` to spawn subprocesses --- pype/modules/ftrack/ftrack_server/socket_thread.py | 11 ++++++----- pype/modules/standalonepublish_action.py | 10 +++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pype/modules/ftrack/ftrack_server/socket_thread.py b/pype/modules/ftrack/ftrack_server/socket_thread.py index c638c9fa03..15681c00b8 100644 --- a/pype/modules/ftrack/ftrack_server/socket_thread.py +++ b/pype/modules/ftrack/ftrack_server/socket_thread.py @@ -6,6 +6,7 @@ import threading import traceback import subprocess from pype.api import Logger +from pype.lib import get_pype_execute_args class SocketThread(threading.Thread): @@ -57,11 +58,11 @@ class SocketThread(threading.Thread): env = os.environ.copy() env["PYPE_PROCESS_MONGO_ID"] = str(Logger.mongo_process_id) - executable_args = [ - sys.executable - ] - if getattr(sys, "frozen", False): - executable_args.append("run") + + # Pype executable + executable_args = get_pype_execute_args() + # Add `run` command + executable_args.append("run") self.subproc = subprocess.Popen( [ diff --git a/pype/modules/standalonepublish_action.py b/pype/modules/standalonepublish_action.py index 4bcb5b6018..8dcc68113d 100644 --- a/pype/modules/standalonepublish_action.py +++ b/pype/modules/standalonepublish_action.py @@ -1,6 +1,7 @@ import os import sys import subprocess +from pype.lib import get_pype_execute_args from . import PypeModule, ITrayAction @@ -29,13 +30,16 @@ class StandAlonePublishAction(PypeModule, ITrayAction): self.publish_paths.extend(publish_paths) def run_standalone_publisher(self): + # TODO add command to cli.py from pype import tools standalone_publisher_tool_path = os.path.join( os.path.dirname(os.path.abspath(tools.__file__)), "standalonepublish" ) - subprocess.Popen([ - sys.executable, + args = [ + *get_pype_execute_args(), + "run", standalone_publisher_tool_path, os.pathsep.join(self.publish_paths).replace("\\", "/") - ]) + ] + subprocess.Popen(args, creationflags=subprocess.DETACHED_PROCESS) From 3f9bc087657d03ffd574bcd38546a38ee554c47b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 15:53:00 +0100 Subject: [PATCH 03/38] fixed executable filename --- pype/lib/execute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/lib/execute.py b/pype/lib/execute.py index 6ad43c5e0d..05357df6ae 100644 --- a/pype/lib/execute.py +++ b/pype/lib/execute.py @@ -150,7 +150,7 @@ def get_pype_execute_args(): pype_executable = os.environ["PYPE_EXECUTABLE"] args = [pype_executable] - executable_filename = os.path.dirname(pype_executable) + executable_filename = os.path.basename(pype_executable) if "python" in executable_filename.lower(): args.append( os.path.join(os.environ["PYPE_ROOT"], "start.py") From 2985925aa076ffa5f003c895be97a56b552059d6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 15:57:19 +0100 Subject: [PATCH 04/38] standalone publisher tool is using pype execute arguments when trigerring pyblish --- pype/tools/standalonepublish/widgets/widget_components.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pype/tools/standalonepublish/widgets/widget_components.py b/pype/tools/standalonepublish/widgets/widget_components.py index 7e0327f00a..1ba282ab83 100644 --- a/pype/tools/standalonepublish/widgets/widget_components.py +++ b/pype/tools/standalonepublish/widgets/widget_components.py @@ -9,6 +9,7 @@ from Qt import QtWidgets, QtCore from . import DropDataFrame from avalon import io from pype.api import execute, Logger +from pype.lib import get_pype_execute_args log = Logger().get_logger("standalonepublisher") @@ -207,8 +208,13 @@ def cli_publish(data, publish_paths, gui=True): if data.get("family", "").lower() == "editorial": envcopy["PYBLISH_SUSPEND_LOGS"] = "1" + args = [ + *get_pype_execute_args(), + "run", + PUBLISH_SCRIPT_PATH + ] result = execute( - [sys.executable, PUBLISH_SCRIPT_PATH], + args, env=envcopy ) From c59cb621b785e51fc3b1b6eeeb25f700619339e5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 16:01:57 +0100 Subject: [PATCH 05/38] fixed legacy ftrack event server --- pype/modules/ftrack/ftrack_server/event_server_cli.py | 8 +++++++- pype/modules/ftrack/ftrack_server/socket_thread.py | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/pype/modules/ftrack/ftrack_server/event_server_cli.py index 96581f0a38..1c05e34f18 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/pype/modules/ftrack/ftrack_server/event_server_cli.py @@ -14,6 +14,7 @@ import uuid import ftrack_api import pymongo +from pype.lib import get_pype_execute_args from pype.modules.ftrack.lib import ( credentials, get_ftrack_url_from_settings @@ -131,8 +132,13 @@ def legacy_server(ftrack_url): if subproc is None: if subproc_failed_count < max_fail_count: + args = [ + *get_pype_execute_args(), + "run", + subproc_path + ] subproc = subprocess.Popen( - ["python", subproc_path], + args, stdout=subprocess.PIPE ) elif subproc_failed_count == max_fail_count: diff --git a/pype/modules/ftrack/ftrack_server/socket_thread.py b/pype/modules/ftrack/ftrack_server/socket_thread.py index 15681c00b8..51b0f6d305 100644 --- a/pype/modules/ftrack/ftrack_server/socket_thread.py +++ b/pype/modules/ftrack/ftrack_server/socket_thread.py @@ -59,14 +59,12 @@ class SocketThread(threading.Thread): env = os.environ.copy() env["PYPE_PROCESS_MONGO_ID"] = str(Logger.mongo_process_id) - # Pype executable - executable_args = get_pype_execute_args() - # Add `run` command - executable_args.append("run") - self.subproc = subprocess.Popen( [ - *executable_args, + # Pype executable (with path to start script if not build) + *get_pype_execute_args(), + # Add `run` command + "run", self.filepath, *self.additional_args, str(self.port) From cae34382a47ecb7c15087b0e9ff41a2bf05ce81d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 16:51:20 +0100 Subject: [PATCH 06/38] have to delete all pype modules from sys.modules --- start.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/start.py b/start.py index ad863481ff..5618920164 100644 --- a/start.py +++ b/start.py @@ -508,9 +508,13 @@ def boot(): os.environ["PYPE_ROOT"], "repos") # delete Pype module from cache so it is used from specific version + modules_to_del = [] + for module_name in sys.modules: + if module_name == "pype" or module_name.startswith("pype."): + modules_to_del.append(module_name) try: - del sys.modules["pype"] - del sys.modules["pype.version"] + for module_name in modules_to_del: + del sys.modules[module_name] except AttributeError: pass except KeyError: From 6ef953134362a23a088c12602a9252361002bb8c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 17:42:39 +0100 Subject: [PATCH 07/38] extract burnin use pype executable to execute burnin script --- pype/plugins/global/publish/extract_burnin.py | 53 ++++++------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 8d1b9c81e3..ee559d10ae 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -4,10 +4,15 @@ import json import copy import tempfile +import pype import pype.api import pyblish -from pype.lib import should_decompress, \ - get_decompress_dir, decompress +from pype.lib import ( + get_pype_execute_args, + should_decompress, + get_decompress_dir, + decompress +) import shutil @@ -125,7 +130,7 @@ class ExtractBurnin(pype.api.Extractor): anatomy = instance.context.data["anatomy"] scriptpath = self.burnin_script_path() - executable = self.python_executable_path() + executable_args = get_pype_execute_args() for idx, repre in enumerate(tuple(instance.data["representations"])): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) @@ -256,17 +261,17 @@ class ExtractBurnin(pype.api.Extractor): ) # Prepare subprocess arguments - args = [ - "\"{}\"".format(executable), - "\"{}\"".format(scriptpath), - "\"{}\"".format(temporary_json_filepath) - ] - subprcs_cmd = " ".join(args) - self.log.debug("Executing: {}".format(subprcs_cmd)) + args = [] + args.extend(executable_args) + args.extend([ + scriptpath, + temporary_json_filepath + ]) + self.log.debug("Executing: {}".format(" ".join(args))) # Run burnin script pype.api.run_subprocess( - subprcs_cmd, shell=True, logger=self.log + args, shell=True, logger=self.log ) # Remove the temporary json @@ -812,19 +817,9 @@ class ExtractBurnin(pype.api.Extractor): def burnin_script_path(self): """Return path to python script for burnin processing.""" - # TODO maybe convert to Plugin's attribute - # Get script path. - module_path = os.environ["PYPE_ROOT"] - - # There can be multiple paths in PYPE_ROOT, in which case - # we just take first one. - if os.pathsep in module_path: - module_path = module_path.split(os.pathsep)[0] - scriptpath = os.path.normpath( os.path.join( - module_path, - "pype", + pype.PACKAGE_DIR, "scripts", "otio_burnin.py" ) @@ -833,17 +828,3 @@ class ExtractBurnin(pype.api.Extractor): self.log.debug("scriptpath: {}".format(scriptpath)) return scriptpath - - def python_executable_path(self): - """Return path to Python 3 executable.""" - # TODO maybe convert to Plugin's attribute - # Get executable. - executable = os.getenv("PYPE_PYTHON_EXE") - - # There can be multiple paths in PYPE_PYTHON_EXE, in which case - # we just take first one. - if os.pathsep in executable: - executable = executable.split(os.pathsep)[0] - - self.log.debug("executable: {}".format(executable)) - return executable From df08fe6a0bce471a223208416914cac5831f251d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 19:38:20 +0100 Subject: [PATCH 08/38] extract burnin can execute pype burnin script --- pype/plugins/global/publish/extract_burnin.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index ee559d10ae..1051ff1a59 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -130,7 +130,15 @@ class ExtractBurnin(pype.api.Extractor): anatomy = instance.context.data["anatomy"] scriptpath = self.burnin_script_path() + # Executable args that will execute the script + # [pype executable, *pype script, "run"] executable_args = get_pype_execute_args() + executable_args.append("run") + + # Environments for script process + env = os.environ.copy() + # pop PYTHONPATH + env.pop("PYTHONPATH", None) for idx, repre in enumerate(tuple(instance.data["representations"])): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) @@ -271,7 +279,7 @@ class ExtractBurnin(pype.api.Extractor): # Run burnin script pype.api.run_subprocess( - args, shell=True, logger=self.log + args, shell=True, logger=self.log, env=env ) # Remove the temporary json From 11b58ed36b1978b489438a7a371c95c531dfb468 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 29 Jan 2021 20:16:58 +0100 Subject: [PATCH 09/38] it is possible to run ftrack event server --- pype/cli.py | 47 ++++------------- .../ftrack/ftrack_server/event_server_cli.py | 50 +++++++++++++++++++ pype/pype_commands.py | 17 ++----- 3 files changed, 66 insertions(+), 48 deletions(-) diff --git a/pype/cli.py b/pype/cli.py index 62975bff31..cf1cd59cd1 100644 --- a/pype/cli.py +++ b/pype/cli.py @@ -88,43 +88,18 @@ def eventserver(debug, """ if debug: os.environ['PYPE_DEBUG'] = "3" - # map eventserver options - # TODO: switch eventserver to click, normalize option names - args = [] - if ftrack_url: - args.append('-ftrackurl') - args.append(ftrack_url) - if ftrack_user: - args.append('-ftrackuser') - args.append(ftrack_user) - - if ftrack_api_key: - args.append('-ftrackapikey') - args.append(ftrack_api_key) - - if ftrack_events_path: - args.append('-ftrackeventpaths') - args.append(ftrack_events_path) - - if no_stored_credentials: - args.append('-noloadcred') - - if store_credentials: - args.append('-storecred') - - if legacy: - args.append('-legacy') - - if clockify_api_key: - args.append('-clockifyapikey') - args.append(clockify_api_key) - - if clockify_workspace: - args.append('-clockifyworkspace') - args.append(clockify_workspace) - - PypeCommands().launch_eventservercli(args) + PypeCommands().launch_eventservercli( + ftrack_url, + ftrack_user, + ftrack_api_key, + ftrack_events_path, + no_stored_credentials, + store_credentials, + legacy, + clockify_api_key, + clockify_workspace + ) @main.command() diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/pype/modules/ftrack/ftrack_server/event_server_cli.py index 1c05e34f18..cc51304fc6 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/pype/modules/ftrack/ftrack_server/event_server_cli.py @@ -420,6 +420,56 @@ def main_loop(ftrack_url): time.sleep(1) +def run_event_server( + ftrack_url, + ftrack_user, + ftrack_api_key, + ftrack_events_path, + no_stored_credentials, + store_credentials, + legacy, + clockify_api_key, + clockify_workspace +): + if not no_stored_credentials: + cred = credentials.get_credentials(ftrack_url) + username = cred.get('username') + api_key = cred.get('api_key') + + if clockify_workspace and clockify_api_key: + os.environ["CLOCKIFY_WORKSPACE"] = clockify_workspace + os.environ["CLOCKIFY_API_KEY"] = clockify_api_key + + # Check url regex and accessibility + ftrack_url = check_ftrack_url(ftrack_url) + if not ftrack_url: + print('Exiting! < Please enter Ftrack server url >') + return 1 + + # Validate entered credentials + if not validate_credentials(ftrack_url, username, api_key): + print('Exiting! < Please enter valid credentials >') + return 1 + + if store_credentials: + credentials.save_credentials(username, api_key, ftrack_url) + + # Set Ftrack environments + os.environ["FTRACK_SERVER"] = ftrack_url + os.environ["FTRACK_API_USER"] = username + os.environ["FTRACK_API_KEY"] = api_key + # TODO This won't work probably + if ftrack_events_path: + if isinstance(ftrack_events_path, (list, tuple)): + ftrack_events_path = os.pathsep.join(ftrack_events_path) + os.environ["FTRACK_EVENTS_PATH"] = ftrack_events_path + + if legacy: + return legacy_server(ftrack_url) + + return main_loop(ftrack_url) + + def main(argv): ''' There are 4 values neccessary for event server: diff --git a/pype/pype_commands.py b/pype/pype_commands.py index 1ec4d2c553..cc597f4beb 100644 --- a/pype/pype_commands.py +++ b/pype/pype_commands.py @@ -28,19 +28,12 @@ class PypeCommands: user_role = "developer" settings.main(user_role) - def launch_eventservercli(self, args): - from pype.modules import ftrack - from pype.lib import execute - - fname = os.path.join( - os.path.dirname(os.path.abspath(ftrack.__file__)), - "ftrack_server", - "event_server_cli.py" + @staticmethod + def launch_eventservercli(*args): + from pype.modules.ftrack.ftrack_server.event_server_cli import ( + run_event_server ) - - return execute([ - sys.executable, "-u", fname - ]) + return run_event_server(*args) def publish(self, gui, paths): pass From b61d515569c61c0e4323bcd5de17ffe4b3c3aa7d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 12:36:54 +0100 Subject: [PATCH 10/38] tools and common vendor are not set to build directory --- start.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/start.py b/start.py index 5618920164..b93500c302 100644 --- a/start.py +++ b/start.py @@ -276,23 +276,13 @@ def _determine_mongodb() -> str: def _initialize_environment(pype_version: PypeVersion) -> None: version_path = pype_version.path os.environ["PYPE_VERSION"] = pype_version.version + # set PYPE_ROOT to point to currently used Pype version. + os.environ["PYPE_ROOT"] = os.path.normpath(version_path.as_posix()) # inject version to Python environment (sys.path, ...) print(">>> Injecting Pype version to running environment ...") bootstrap.add_paths_from_directory(version_path) - # add venv 'site-packages' to PYTHONPATH - python_path = os.getenv("PYTHONPATH", "") - split_paths = python_path.split(os.pathsep) - # add pype tools - split_paths.append(os.path.join(os.environ["PYPE_ROOT"], "pype", "tools")) - # add common pype vendor - # (common for multiple Python interpreter versions) - split_paths.append(os.path.join( - os.environ["PYPE_ROOT"], "pype", "vendor", "python", "common")) - os.environ["PYTHONPATH"] = os.pathsep.join(split_paths) - # set PYPE_ROOT to point to currently used Pype version. - os.environ["PYPE_ROOT"] = os.path.normpath(version_path.as_posix()) def _find_frozen_pype(use_version: str = None, From 96c8f922d875cb4e5449f289fa69e2669d979960 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 12:39:06 +0100 Subject: [PATCH 11/38] frozen pype adds sys paths to tools and common vendor to right dirs --- start.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/start.py b/start.py index b93500c302..35b80503bf 100644 --- a/start.py +++ b/start.py @@ -282,7 +282,28 @@ def _initialize_environment(pype_version: PypeVersion) -> None: print(">>> Injecting Pype version to running environment ...") bootstrap.add_paths_from_directory(version_path) + # Additional sys paths related to PYPE_ROOT directory + additional_paths = [ + # add pype tools + os.path.join(os.environ["PYPE_ROOT"], "pype", "pype", "tools"), + # add common pype vendor + # (common for multiple Python interpreter versions) + os.path.join( + os.environ["PYPE_ROOT"], + "pype", + "pype", + "vendor", + "python", + "common" + ) + ] + split_paths = os.getenv("PYTHONPATH", "").split(os.pathsep) + for path in additional_paths: + split_paths.insert(0, path) + sys.path.insert(0, path) + + os.environ["PYTHONPATH"] = os.pathsep.join(split_paths) def _find_frozen_pype(use_version: str = None, From 64806743456c61fcf1387420287917a72645f0dd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 12:39:53 +0100 Subject: [PATCH 12/38] sys paths to tools and common vendor are in fron code initialization added only if not executed from frozen code --- start.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/start.py b/start.py index 35b80503bf..3e5d1715ed 100644 --- a/start.py +++ b/start.py @@ -433,17 +433,24 @@ def _bootstrap_from_code(use_version): python_path = os.getenv("PYTHONPATH", "") split_paths = python_path.split(os.pathsep) split_paths += repos - # add pype tools - split_paths.append(os.path.join(os.environ["PYPE_ROOT"], "pype", "tools")) # last one should be venv site-packages # this is slightly convoluted as we can get here from frozen code too # in case when we are running without any version installed. if not getattr(sys, 'frozen', False): split_paths.append(site.getsitepackages()[-1]) - # add common pype vendor - # (common for multiple Python interpreter versions) - split_paths.append(os.path.join( - os.environ["PYPE_ROOT"], "pype", "vendor", "python", "common")) + additional_paths = [ + # add pype tools + os.path.join(os.environ["PYPE_ROOT"], "pype", "tools"), + # add common pype vendor + # (common for multiple Python interpreter versions) + os.path.join( + os.environ["PYPE_ROOT"], "pype", "vendor", "python", "common" + ) + ] + for path in additional_paths: + split_paths.insert(0, path) + sys.path.insert(0, path) + os.environ["PYTHONPATH"] = os.pathsep.join(split_paths) return Path(version_path) From 5666f90879c86c65c3da97e840318f298e142b6e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 12:40:20 +0100 Subject: [PATCH 13/38] added TODO comments to tools and common vendor sys paths --- start.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/start.py b/start.py index 3e5d1715ed..4453e912f0 100644 --- a/start.py +++ b/start.py @@ -283,6 +283,8 @@ def _initialize_environment(pype_version: PypeVersion) -> None: bootstrap.add_paths_from_directory(version_path) # Additional sys paths related to PYPE_ROOT directory + # TODO move additional paths to `boot` part when PYPE_ROOT will point + # to same hierarchy from code and from frozen pype additional_paths = [ # add pype tools os.path.join(os.environ["PYPE_ROOT"], "pype", "pype", "tools"), @@ -438,6 +440,8 @@ def _bootstrap_from_code(use_version): # in case when we are running without any version installed. if not getattr(sys, 'frozen', False): split_paths.append(site.getsitepackages()[-1]) + # TODO move additional paths to `boot` part when PYPE_ROOT will point + # to same hierarchy from code and from frozen pype additional_paths = [ # add pype tools os.path.join(os.environ["PYPE_ROOT"], "pype", "tools"), From 161b0c14a06530676f6ccd1eac35e006c42fb5ac Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 12:41:00 +0100 Subject: [PATCH 14/38] prepend repo paths to sys.path instead of appending --- start.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/start.py b/start.py index 4453e912f0..93fff22e0c 100644 --- a/start.py +++ b/start.py @@ -429,12 +429,13 @@ def _bootstrap_from_code(use_version): # add self to python paths repos.insert(0, pype_root) for repo in repos: - sys.path.append(repo) + sys.path.insert(0, repo) # add venv 'site-packages' to PYTHONPATH python_path = os.getenv("PYTHONPATH", "") split_paths = python_path.split(os.pathsep) - split_paths += repos + # Add repos as first in list + split_paths = repos + split_paths # last one should be venv site-packages # this is slightly convoluted as we can get here from frozen code too # in case when we are running without any version installed. From 5a7bf4106b19da14cc316c9aab9d77a756676b9d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 12:41:12 +0100 Subject: [PATCH 15/38] added warning comment --- start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.py b/start.py index 93fff22e0c..5e0532e2a7 100644 --- a/start.py +++ b/start.py @@ -508,7 +508,7 @@ def boot(): # ------------------------------------------------------------------------ # Find Pype versions # ------------------------------------------------------------------------ - + # WARNING Environment PYPE_ROOT may change if frozen pype is executed if getattr(sys, 'frozen', False): # find versions of Pype to be used with frozen code try: From 61ef0cbb0d299f1e4d0e84485c62deb521a5f1d1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 12:41:23 +0100 Subject: [PATCH 16/38] pype modules are also popped --- start.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/start.py b/start.py index 5e0532e2a7..0679d8e39f 100644 --- a/start.py +++ b/start.py @@ -530,11 +530,12 @@ def boot(): os.environ["PYPE_REPOS_ROOT"] = os.path.join( os.environ["PYPE_ROOT"], "repos") - # delete Pype module from cache so it is used from specific version + # delete Pype module and it's submodules from cache so it is used from + # specific version modules_to_del = [] - for module_name in sys.modules: + for module_name in tuple(sys.modules): if module_name == "pype" or module_name.startswith("pype."): - modules_to_del.append(module_name) + modules_to_del.append(sys.modules.pop(module_name)) try: for module_name in modules_to_del: del sys.modules[module_name] From 1f42ebc7366ce0fa9377818d3f73ae66d4c08973 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 1 Feb 2021 13:02:24 +0100 Subject: [PATCH 17/38] standalone publisher has it's own cli command --- pype/cli.py | 6 ++++ pype/modules/standalonepublish_action.py | 10 +----- pype/pype_commands.py | 5 +++ pype/tools/standalonepublish/__init__.py | 14 ++++---- pype/tools/standalonepublish/__main__.py | 35 ------------------- pype/tools/standalonepublish/app.py | 44 ++++++++++++++++++++++-- 6 files changed, 62 insertions(+), 52 deletions(-) delete mode 100644 pype/tools/standalonepublish/__main__.py diff --git a/pype/cli.py b/pype/cli.py index cf1cd59cd1..137ae327b2 100644 --- a/pype/cli.py +++ b/pype/cli.py @@ -31,6 +31,12 @@ def settings(dev=False): PypeCommands().launch_settings_gui(dev) +@main.command() +def standalonepublisher(): + """Show Pype Standalone publisher UI.""" + PypeCommands().launch_standalone_publisher() + + @main.command() @click.option("-d", "--debug", is_flag=True, help=("Run pype tray in debug mode")) diff --git a/pype/modules/standalonepublish_action.py b/pype/modules/standalonepublish_action.py index 8dcc68113d..04f6fac87a 100644 --- a/pype/modules/standalonepublish_action.py +++ b/pype/modules/standalonepublish_action.py @@ -30,16 +30,8 @@ class StandAlonePublishAction(PypeModule, ITrayAction): self.publish_paths.extend(publish_paths) def run_standalone_publisher(self): - # TODO add command to cli.py - from pype import tools - standalone_publisher_tool_path = os.path.join( - os.path.dirname(os.path.abspath(tools.__file__)), - "standalonepublish" - ) args = [ *get_pype_execute_args(), - "run", - standalone_publisher_tool_path, - os.pathsep.join(self.publish_paths).replace("\\", "/") + "standalonepublisher" ] subprocess.Popen(args, creationflags=subprocess.DETACHED_PROCESS) diff --git a/pype/pype_commands.py b/pype/pype_commands.py index cc597f4beb..58a3fe738c 100644 --- a/pype/pype_commands.py +++ b/pype/pype_commands.py @@ -35,6 +35,11 @@ class PypeCommands: ) return run_event_server(*args) + @staticmethod + def launch_standalone_publisher(): + from pype.tools import standalonepublish + standalonepublish.main() + def publish(self, gui, paths): pass diff --git a/pype/tools/standalonepublish/__init__.py b/pype/tools/standalonepublish/__init__.py index 29a4e52904..d2ef73af00 100644 --- a/pype/tools/standalonepublish/__init__.py +++ b/pype/tools/standalonepublish/__init__.py @@ -1,8 +1,10 @@ from .app import ( - show, - cli + main, + Window +) + + +__all__ = ( + "main", + "Window" ) -__all__ = [ - "show", - "cli" -] diff --git a/pype/tools/standalonepublish/__main__.py b/pype/tools/standalonepublish/__main__.py deleted file mode 100644 index 85a574f8dc..0000000000 --- a/pype/tools/standalonepublish/__main__.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -import sys -import app -import ctypes -import signal -from Qt import QtWidgets, QtGui -from avalon import style -from pype.api import resources - - -if __name__ == "__main__": - - # Allow to change icon of running process in windows taskbar - if os.name == "nt": - ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( - u"standalonepublish" - ) - - qt_app = QtWidgets.QApplication([]) - # app.setQuitOnLastWindowClosed(False) - qt_app.setStyleSheet(style.load_stylesheet()) - icon = QtGui.QIcon(resources.pype_icon_filepath()) - qt_app.setWindowIcon(icon) - - def signal_handler(sig, frame): - print("You pressed Ctrl+C. Process ended.") - qt_app.quit() - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - window = app.Window(sys.argv[-1].split(os.pathsep)) - window.show() - - sys.exit(qt_app.exec_()) diff --git a/pype/tools/standalonepublish/app.py b/pype/tools/standalonepublish/app.py index a22dae32b9..920dd32f7c 100644 --- a/pype/tools/standalonepublish/app.py +++ b/pype/tools/standalonepublish/app.py @@ -1,7 +1,18 @@ +import os +import sys +import ctypes +import signal + from bson.objectid import ObjectId -from Qt import QtWidgets, QtCore -from widgets import AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget +from Qt import QtWidgets, QtCore, QtGui + +from .widgets import ( + AssetWidget, FamilyWidget, ComponentsWidget, ShadowWidget +) +from avalon import style +from pype.api import resources from avalon.api import AvalonMongoDB +from pype.modules import ModulesManager class Window(QtWidgets.QDialog): @@ -194,3 +205,32 @@ class Window(QtWidgets.QDialog): data.update(self.widget_components.collect_data()) return data + + +def main(): + # Allow to change icon of running process in windows taskbar + if os.name == "nt": + ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( + u"standalonepublish" + ) + + qt_app = QtWidgets.QApplication([]) + # app.setQuitOnLastWindowClosed(False) + qt_app.setStyleSheet(style.load_stylesheet()) + icon = QtGui.QIcon(resources.pype_icon_filepath()) + qt_app.setWindowIcon(icon) + + def signal_handler(sig, frame): + print("You pressed Ctrl+C. Process ended.") + qt_app.quit() + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + modules_manager = ModulesManager() + module = modules_manager.modules_by_name["standalonepublish_tool"] + + window = Window(module.publish_paths) + window.show() + + sys.exit(qt_app.exec_()) From 42b12ed28fb9be56f5ecaab093530539d42c2e32 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 2 Feb 2021 10:38:08 +0100 Subject: [PATCH 18/38] get_pype_execute_args can accept any number of arguments that will be added after executable --- pype/lib/execute.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pype/lib/execute.py b/pype/lib/execute.py index 05357df6ae..7e37e5d6da 100644 --- a/pype/lib/execute.py +++ b/pype/lib/execute.py @@ -135,7 +135,7 @@ def run_subprocess(*args, **kwargs): return full_output -def get_pype_execute_args(): +def get_pype_execute_args(*args): """Arguments to run pype command. Arguments for subprocess when need to spawn new pype process. Which may be @@ -146,13 +146,20 @@ def get_pype_execute_args(): Pype executed from code has different executable set to virtual env python and must have path to script as first argument which is not needed for build pype. + + It is possible to pass any arguments that will be added after pype + executables. """ pype_executable = os.environ["PYPE_EXECUTABLE"] - args = [pype_executable] + pype_args = [pype_executable] executable_filename = os.path.basename(pype_executable) if "python" in executable_filename.lower(): - args.append( + pype_args.append( os.path.join(os.environ["PYPE_ROOT"], "start.py") ) - return args + + if args: + pype_args.extend(args) + + return pype_args From 052ca7f08685d036c81533c3998b442185abf296 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 2 Feb 2021 10:39:04 +0100 Subject: [PATCH 19/38] simplified usage of get_pype_execute_args --- .../ftrack/ftrack_server/event_server_cli.py | 6 +----- .../ftrack/ftrack_server/socket_thread.py | 21 +++++++------------ pype/modules/standalonepublish_action.py | 5 +---- pype/plugins/global/publish/extract_burnin.py | 11 +++------- .../widgets/widget_components.py | 11 ++-------- 5 files changed, 15 insertions(+), 39 deletions(-) diff --git a/pype/modules/ftrack/ftrack_server/event_server_cli.py b/pype/modules/ftrack/ftrack_server/event_server_cli.py index cc51304fc6..27b25bd8cf 100644 --- a/pype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/pype/modules/ftrack/ftrack_server/event_server_cli.py @@ -132,11 +132,7 @@ def legacy_server(ftrack_url): if subproc is None: if subproc_failed_count < max_fail_count: - args = [ - *get_pype_execute_args(), - "run", - subproc_path - ] + args = get_pype_execute_args("run", subproc_path) subproc = subprocess.Popen( args, stdout=subprocess.PIPE diff --git a/pype/modules/ftrack/ftrack_server/socket_thread.py b/pype/modules/ftrack/ftrack_server/socket_thread.py index 51b0f6d305..a895e0b900 100644 --- a/pype/modules/ftrack/ftrack_server/socket_thread.py +++ b/pype/modules/ftrack/ftrack_server/socket_thread.py @@ -58,20 +58,15 @@ class SocketThread(threading.Thread): env = os.environ.copy() env["PYPE_PROCESS_MONGO_ID"] = str(Logger.mongo_process_id) - - self.subproc = subprocess.Popen( - [ - # Pype executable (with path to start script if not build) - *get_pype_execute_args(), - # Add `run` command - "run", - self.filepath, - *self.additional_args, - str(self.port) - ], - env=env, - stdin=subprocess.PIPE + # Pype executable (with path to start script if not build) + args = get_pype_execute_args( + # Add `run` command + "run", + self.filepath, + *self.additional_args, + str(self.port) ) + self.subproc = subprocess.Popen(args, env=env, stdin=subprocess.PIPE) # Listen for incoming connections sock.listen(1) diff --git a/pype/modules/standalonepublish_action.py b/pype/modules/standalonepublish_action.py index 04f6fac87a..3cfee67c85 100644 --- a/pype/modules/standalonepublish_action.py +++ b/pype/modules/standalonepublish_action.py @@ -30,8 +30,5 @@ class StandAlonePublishAction(PypeModule, ITrayAction): self.publish_paths.extend(publish_paths) def run_standalone_publisher(self): - args = [ - *get_pype_execute_args(), - "standalonepublisher" - ] + args = get_pype_execute_args("standalonepublisher") subprocess.Popen(args, creationflags=subprocess.DETACHED_PROCESS) diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index 1051ff1a59..f3276972e6 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -132,8 +132,7 @@ class ExtractBurnin(pype.api.Extractor): scriptpath = self.burnin_script_path() # Executable args that will execute the script # [pype executable, *pype script, "run"] - executable_args = get_pype_execute_args() - executable_args.append("run") + executable_args = get_pype_execute_args("run", scriptpath) # Environments for script process env = os.environ.copy() @@ -269,12 +268,8 @@ class ExtractBurnin(pype.api.Extractor): ) # Prepare subprocess arguments - args = [] - args.extend(executable_args) - args.extend([ - scriptpath, - temporary_json_filepath - ]) + args = list(executable_args) + args.append(temporary_json_filepath) self.log.debug("Executing: {}".format(" ".join(args))) # Run burnin script diff --git a/pype/tools/standalonepublish/widgets/widget_components.py b/pype/tools/standalonepublish/widgets/widget_components.py index 1ba282ab83..8d627b7eed 100644 --- a/pype/tools/standalonepublish/widgets/widget_components.py +++ b/pype/tools/standalonepublish/widgets/widget_components.py @@ -208,15 +208,8 @@ def cli_publish(data, publish_paths, gui=True): if data.get("family", "").lower() == "editorial": envcopy["PYBLISH_SUSPEND_LOGS"] = "1" - args = [ - *get_pype_execute_args(), - "run", - PUBLISH_SCRIPT_PATH - ] - result = execute( - args, - env=envcopy - ) + args = get_pype_execute_args("run", PUBLISH_SCRIPT_PATH) + result = execute(args, env=envcopy) result = {} if os.path.exists(json_data_path): From b8e0a7479d7625bb641f011f68cf1b80d966832d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 2 Feb 2021 10:40:09 +0100 Subject: [PATCH 20/38] added standalone publisher to silent commands --- start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.py b/start.py index 0679d8e39f..5a34bbc11a 100644 --- a/start.py +++ b/start.py @@ -116,7 +116,7 @@ from igniter.tools import load_environments # noqa: E402 from igniter.bootstrap_repos import PypeVersion # noqa: E402 bootstrap = BootstrapRepos() -silent_commands = ["run", "igniter"] +silent_commands = ["run", "igniter", "standalonepublisher"] def set_environments() -> None: From c6f9cb708397f5e37e837c4d5660fe4441a920bd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 3 Feb 2021 15:31:09 +0100 Subject: [PATCH 21/38] crashed action with log traceback --- pype/modules/ftrack/actions/action_create_project_structure.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/modules/ftrack/actions/action_create_project_structure.py b/pype/modules/ftrack/actions/action_create_project_structure.py index 64b4ba6727..e5d0777570 100644 --- a/pype/modules/ftrack/actions/action_create_project_structure.py +++ b/pype/modules/ftrack/actions/action_create_project_structure.py @@ -91,6 +91,7 @@ class CreateProjectFolders(BaseAction): self.create_ftrack_entities(basic_paths, project_entity) except Exception as exc: + self.log.warning("Creating of structure crashed.", exc_info=True) session.rollback() return { "success": False, From 62479df77c7d02c5aee75830ae56bad5d579e609 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 3 Feb 2021 15:33:13 +0100 Subject: [PATCH 22/38] anatomy is created in create_folders and entity is not passed --- .../ftrack/actions/action_create_project_structure.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_project_structure.py b/pype/modules/ftrack/actions/action_create_project_structure.py index e5d0777570..4f9579bb12 100644 --- a/pype/modules/ftrack/actions/action_create_project_structure.py +++ b/pype/modules/ftrack/actions/action_create_project_structure.py @@ -86,8 +86,7 @@ class CreateProjectFolders(BaseAction): try: # Get paths based on presets basic_paths = self.get_path_items(project_folder_structure) - anatomy = Anatomy(project_entity["full_name"]) - self.create_folders(basic_paths, project_entity, anatomy) + self.create_folders(basic_paths, project_entity) self.create_ftrack_entities(basic_paths, project_entity) except Exception as exc: @@ -221,7 +220,8 @@ class CreateProjectFolders(BaseAction): output.append(os.path.normpath(os.path.sep.join(clean_items))) return output - def create_folders(self, basic_paths, project, anatomy): + def create_folders(self, basic_paths, project): + anatomy = Anatomy(project["full_name"]) roots_paths = [] if isinstance(anatomy.roots, dict): for root in anatomy.roots: From e31bd5b75a40a673e7f73d2d7d761414626106e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 3 Feb 2021 15:43:40 +0100 Subject: [PATCH 23/38] don't use root keys but their path values --- pype/modules/ftrack/actions/action_create_project_structure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/actions/action_create_project_structure.py b/pype/modules/ftrack/actions/action_create_project_structure.py index 4f9579bb12..6b02a84357 100644 --- a/pype/modules/ftrack/actions/action_create_project_structure.py +++ b/pype/modules/ftrack/actions/action_create_project_structure.py @@ -224,7 +224,7 @@ class CreateProjectFolders(BaseAction): anatomy = Anatomy(project["full_name"]) roots_paths = [] if isinstance(anatomy.roots, dict): - for root in anatomy.roots: + for root in anatomy.roots.values(): roots_paths.append(root.value) else: roots_paths.append(anatomy.roots.value) From 70646a9907f480c688de27016a1e03de89a08397 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 3 Feb 2021 15:43:49 +0100 Subject: [PATCH 24/38] fix existence cehcek and added more debug logs --- .../ftrack/actions/action_create_project_structure.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/actions/action_create_project_structure.py b/pype/modules/ftrack/actions/action_create_project_structure.py index 6b02a84357..f42f952314 100644 --- a/pype/modules/ftrack/actions/action_create_project_structure.py +++ b/pype/modules/ftrack/actions/action_create_project_structure.py @@ -234,9 +234,14 @@ class CreateProjectFolders(BaseAction): full_paths = self.compute_paths(basic_paths, project_root) # Create folders for path in full_paths: - if os.path.exists(path): - continue - os.makedirs(path.format(project_root=project_root)) + full_path = path.format(project_root=project_root) + if os.path.exists(full_path): + self.log.debug( + "Folder already exists: {}".format(full_path) + ) + else: + self.log.debug("Creating folder: {}".format(full_path)) + os.makedirs(full_path) def register(session): From a8394304100b103db1798124b702c7ed42e9b917 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 3 Feb 2021 19:43:55 +0100 Subject: [PATCH 25/38] fixed tasks changes on project update --- pype/modules/ftrack/lib/avalon_sync.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 2e9d8d68ed..cd6a1e69ac 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -2124,10 +2124,27 @@ class SyncEntitiesFactory: self.report_items["warning"][msg] = sub_msg self.log.warning(sub_msg) - return self.compare_dict( - self.entities_dict[self.ft_project_id]["final_entity"], - self.avalon_project - ) + # Compare tasks from current project schema and previous project schema + final_doc_data = self.entities_dict[self.ft_project_id]["final_entity"] + final_doc_tasks = final_doc_data["config"].pop("tasks") + current_doc_tasks = self.avalon_project.get("config", {}).get("tasks") + # Update project's tasks if tasks are empty or are not same + if not final_doc_tasks: + update_tasks = True + else: + update_tasks = final_doc_tasks != current_doc_tasks + + changes = self.compare_dict(final_doc_data, self.avalon_project) + + # Put back tasks data to final entity object + final_doc_data["config"]["tasks"] = final_doc_tasks + + # Add tasks updates if tasks changed + if update_tasks: + if "config" not in changes: + changes["config"] = {} + changes["config"]["tasks"] = final_doc_tasks + return changes def compare_dict(self, dict_new, dict_old, _ignore_keys=[]): """ From 8e4adae3cac23a8b8fe59f812a13460bb5468bb3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 3 Feb 2021 19:44:16 +0100 Subject: [PATCH 26/38] do same modification in assets synchronization --- pype/modules/ftrack/lib/avalon_sync.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index cd6a1e69ac..e9dc1734c6 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -1674,9 +1674,18 @@ class SyncEntitiesFactory: avalon_id ) ) + # Prepare task changes as they have to be stored as one key + final_doc = self.entities_dict[ftrack_id]["final_entity"] + final_doc_tasks = final_doc["data"].pop("tasks", None) or {} + current_doc_tasks = avalon_entity["data"].get("tasks") or {} + if not final_doc_tasks: + update_tasks = True + else: + update_tasks = final_doc_tasks != current_doc_tasks + # check rest of data data_changes = self.compare_dict( - self.entities_dict[ftrack_id]["final_entity"], + final_doc, avalon_entity, ignore_keys[ftrack_id] ) @@ -1686,17 +1695,13 @@ class SyncEntitiesFactory: self.updates[avalon_id] ) - # double check changes in tasks, some task could be renamed or - # deleted in Ftrack - not captured otherwise - final_entity = self.entities_dict[ftrack_id]["final_entity"] - if final_entity["data"].get("tasks", {}) != \ - avalon_entity["data"].get("tasks", {}): + # Add tasks back to final doc object + final_doc["data"]["tasks"] = final_doc_tasks + # Add tasks to updates if there are different + if update_tasks: if "data" not in self.updates[avalon_id]: self.updates[avalon_id]["data"] = {} - - self.updates[avalon_id]["data"]["tasks"] = ( - final_entity["data"]["tasks"] - ) + self.updates[avalon_id]["data"]["tasks"] = final_doc_tasks def synchronize(self): self.log.debug("* Synchronization begins") From 62c2dd22329f4b4cec3a10f3e322cff279835c0d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Feb 2021 11:09:15 +0100 Subject: [PATCH 27/38] moved ftrack plugins to ftrack module --- .../ftrack/plugins}/_unused_publish/integrate_ftrack_comments.py | 0 .../ftrack/plugins}/publish/collect_ftrack_api.py | 0 .../ftrack/plugins}/publish/integrate_ftrack_api.py | 0 .../plugins}/publish/integrate_ftrack_component_overwrite.py | 0 .../ftrack/plugins}/publish/integrate_ftrack_instances.py | 0 .../ftrack/plugins}/publish/integrate_ftrack_note.py | 0 .../ftrack/plugins}/publish/integrate_hierarchy_ftrack.py | 0 .../ftrack/plugins}/publish/integrate_hierarchy_ftrack_SP.py | 0 .../ftrack/plugins}/publish/integrate_remove_components.py | 0 .../ftrack/plugins}/publish/validate_custom_ftrack_attributes.py | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename pype/{plugins/ftrack => modules/ftrack/plugins}/_unused_publish/integrate_ftrack_comments.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/collect_ftrack_api.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/integrate_ftrack_api.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/integrate_ftrack_component_overwrite.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/integrate_ftrack_instances.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/integrate_ftrack_note.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/integrate_hierarchy_ftrack.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/integrate_hierarchy_ftrack_SP.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/integrate_remove_components.py (100%) rename pype/{plugins/ftrack => modules/ftrack/plugins}/publish/validate_custom_ftrack_attributes.py (100%) diff --git a/pype/plugins/ftrack/_unused_publish/integrate_ftrack_comments.py b/pype/modules/ftrack/plugins/_unused_publish/integrate_ftrack_comments.py similarity index 100% rename from pype/plugins/ftrack/_unused_publish/integrate_ftrack_comments.py rename to pype/modules/ftrack/plugins/_unused_publish/integrate_ftrack_comments.py diff --git a/pype/plugins/ftrack/publish/collect_ftrack_api.py b/pype/modules/ftrack/plugins/publish/collect_ftrack_api.py similarity index 100% rename from pype/plugins/ftrack/publish/collect_ftrack_api.py rename to pype/modules/ftrack/plugins/publish/collect_ftrack_api.py diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_api.py b/pype/modules/ftrack/plugins/publish/integrate_ftrack_api.py similarity index 100% rename from pype/plugins/ftrack/publish/integrate_ftrack_api.py rename to pype/modules/ftrack/plugins/publish/integrate_ftrack_api.py diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_component_overwrite.py b/pype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py similarity index 100% rename from pype/plugins/ftrack/publish/integrate_ftrack_component_overwrite.py rename to pype/modules/ftrack/plugins/publish/integrate_ftrack_component_overwrite.py diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py similarity index 100% rename from pype/plugins/ftrack/publish/integrate_ftrack_instances.py rename to pype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_note.py b/pype/modules/ftrack/plugins/publish/integrate_ftrack_note.py similarity index 100% rename from pype/plugins/ftrack/publish/integrate_ftrack_note.py rename to pype/modules/ftrack/plugins/publish/integrate_ftrack_note.py diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py similarity index 100% rename from pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py rename to pype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack.py diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack_SP.py b/pype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack_SP.py similarity index 100% rename from pype/plugins/ftrack/publish/integrate_hierarchy_ftrack_SP.py rename to pype/modules/ftrack/plugins/publish/integrate_hierarchy_ftrack_SP.py diff --git a/pype/plugins/ftrack/publish/integrate_remove_components.py b/pype/modules/ftrack/plugins/publish/integrate_remove_components.py similarity index 100% rename from pype/plugins/ftrack/publish/integrate_remove_components.py rename to pype/modules/ftrack/plugins/publish/integrate_remove_components.py diff --git a/pype/plugins/ftrack/publish/validate_custom_ftrack_attributes.py b/pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py similarity index 100% rename from pype/plugins/ftrack/publish/validate_custom_ftrack_attributes.py rename to pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py From 8b387ded9e042edb33a46ea1f4b813547226ed78 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Feb 2021 11:09:31 +0100 Subject: [PATCH 28/38] changed path to ftrack plugins that ftrack module returns --- pype/modules/ftrack/ftrack_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/ftrack_module.py b/pype/modules/ftrack/ftrack_module.py index d2de27e1b9..2cbd79c32e 100644 --- a/pype/modules/ftrack/ftrack_module.py +++ b/pype/modules/ftrack/ftrack_module.py @@ -63,7 +63,7 @@ class FtrackModule( def get_plugin_paths(self): """Ftrack plugin paths.""" return { - "publish": [os.path.join(pype.PLUGINS_DIR, "ftrack", "publish")] + "publish": [os.path.join(FTRACK_MODULE_DIR, "plugins", "publish")] } def get_launch_hook_paths(self): From 8d8032404400b5ccdc39aa0b2bcdbd9413ba3a00 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Feb 2021 11:09:57 +0100 Subject: [PATCH 29/38] fixed syntax error in validate_custom_ftrack_attributes plugin --- .../ftrack/plugins/publish/validate_custom_ftrack_attributes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py b/pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py index b141a8bcb7..03aa8844fd 100644 --- a/pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py +++ b/pype/modules/ftrack/plugins/publish/validate_custom_ftrack_attributes.py @@ -61,7 +61,7 @@ class ValidateFtrackAttributes(pyblish.api.InstancePlugin): "Missing FTrack Task entity in context") host = pyblish.api.current_host() - to_check = self.ftrack_custom_attributes.get(host, {})) + to_check = self.ftrack_custom_attributes.get(host, {}) if not to_check: self.log.warning("ftrack_attributes preset not found") From c2bbc70bc4b4ba75e72ff80faf49c066a2a59715 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 4 Feb 2021 11:43:40 +0100 Subject: [PATCH 30/38] nuke: refactory mov loader --- pype/hosts/nuke/plugins/load/load_mov.py | 85 +++++++++--------------- 1 file changed, 30 insertions(+), 55 deletions(-) diff --git a/pype/hosts/nuke/plugins/load/load_mov.py b/pype/hosts/nuke/plugins/load/load_mov.py index f9be675cb9..435f26ad98 100644 --- a/pype/hosts/nuke/plugins/load/load_mov.py +++ b/pype/hosts/nuke/plugins/load/load_mov.py @@ -43,32 +43,6 @@ def preserve_trim(node): "{}".format(script_start)) -def loader_shift(node, frame, relative=True): - """Shift global in time by i preserving duration - - This moves the loader by i frames preserving global duration. When relative - is False it will shift the global in to the start frame. - - Args: - loader (tool): The fusion loader tool. - frame (int): The amount of frames to move. - relative (bool): When True the shift is relative, else the shift will - change the global in to frame. - - Returns: - int: The resulting relative frame change (how much it moved) - - """ - # working script frame range - script_start = nuke.root()["first_frame"].value() - - if relative: - node['frame_mode'].setValue("start at") - node['frame'].setValue(str(frame)) - - return int(script_start) - - def add_review_presets_config(): returning = { "families": list(), @@ -114,6 +88,8 @@ class LoadMov(api.Loader): icon = "code-fork" color = "orange" + script_start = nuke.root()["first_frame"].value() + def load(self, context, name, namespace, data): from avalon.nuke import ( containerise, @@ -142,8 +118,6 @@ class LoadMov(api.Loader): context["representation"]["_id"] # create handles offset (only to last, because of mov) last += handle_start + handle_end - # offset should be with handles so it match orig frame range - offset_frame = orig_first - handle_start # Fallback to asset name when namespace is None if namespace is None: @@ -171,13 +145,14 @@ class LoadMov(api.Loader): ) read_node["file"].setValue(file) - loader_shift(read_node, first, relative=True) read_node["origfirst"].setValue(first) read_node["first"].setValue(first) read_node["origlast"].setValue(last) read_node["last"].setValue(last) - read_node["frame_mode"].setValue("start at") - read_node["frame"].setValue(str(offset_frame)) + + # start at script start + read_node['frame_mode'].setValue("start at") + read_node['frame'].setValue(str(self.script_start)) if colorspace: read_node["colorspace"].setValue(str(colorspace)) @@ -233,9 +208,9 @@ class LoadMov(api.Loader): update_container ) - node = nuke.toNode(container['objectName']) + read_node = nuke.toNode(container['objectName']) - assert node.Class() == "Read", "Must be Read" + assert read_node.Class() == "Read", "Must be Read" file = self.fname @@ -280,7 +255,7 @@ class LoadMov(api.Loader): "Missing start frame for updated version" "assuming starts at frame 0 for: " "{} ({})").format( - node['name'].value(), representation)) + read_node['name'].value(), representation)) first = 0 # fix handle start and end if none are available @@ -290,30 +265,30 @@ class LoadMov(api.Loader): # create handles offset (only to last, because of mov) last += handle_start + handle_end - # offset should be with handles so it match orig frame range - offset_frame = orig_first - handle_start # Update the loader's path whilst preserving some values - with preserve_trim(node): - node["file"].setValue(file) - self.log.info("__ node['file']: {}".format(node["file"].value())) + with preserve_trim(read_node): + read_node["file"].setValue(file) + self.log.info("__ node['file']: {}".format( + read_node["file"].value())) - # Set the global in to the start frame of the sequence - loader_shift(node, first, relative=True) - node["origfirst"].setValue(first) - node["first"].setValue(first) - node["origlast"].setValue(last) - node["last"].setValue(last) - node["frame_mode"].setValue("start at") - node["frame"].setValue(str(offset_frame)) + # Set the global in to the start frame of the sequence + read_node["origfirst"].setValue(first) + read_node["first"].setValue(first) + read_node["origlast"].setValue(last) + read_node["last"].setValue(last) - if colorspace: - node["colorspace"].setValue(str(colorspace)) + # start at script start + read_node['frame_mode'].setValue("start at") + read_node['frame'].setValue(str(self.script_start)) - preset_clrsp = get_imageio_input_colorspace(file) + if colorspace: + read_node["colorspace"].setValue(str(colorspace)) - if preset_clrsp is not None: - node["colorspace"].setValue(preset_clrsp) + preset_clrsp = get_imageio_input_colorspace(file) + + if preset_clrsp is not None: + read_node["colorspace"].setValue(preset_clrsp) updated_dict = {} updated_dict.update({ @@ -332,13 +307,13 @@ class LoadMov(api.Loader): # change color of node if version.get("name") not in [max_version]: - node["tile_color"].setValue(int("0xd84f20ff", 16)) + read_node["tile_color"].setValue(int("0xd84f20ff", 16)) else: - node["tile_color"].setValue(int("0x4ecd25ff", 16)) + read_node["tile_color"].setValue(int("0x4ecd25ff", 16)) # Update the imprinted representation update_container( - node, updated_dict + read_node, updated_dict ) self.log.info("udated to version: {}".format(version.get("name"))) From 6bdda43a7f6446e113684427ba811ab99bf101b4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Feb 2021 18:06:25 +0100 Subject: [PATCH 31/38] implemented get_creator_by_name in pype.lib.avalon_context --- pype/lib/__init__.py | 6 +++++- pype/lib/avalon_context.py | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/pype/lib/__init__.py b/pype/lib/__init__.py index 59eb2a645c..bbc32a5e8a 100644 --- a/pype/lib/__init__.py +++ b/pype/lib/__init__.py @@ -59,7 +59,9 @@ from .avalon_context import ( save_workfile_data_to_doc, get_workfile_doc, - BuildWorkfile + BuildWorkfile, + + get_creator_by_name ) from .applications import ( @@ -141,6 +143,8 @@ __all__ = [ "BuildWorkfile", + "get_creator_by_name", + "ApplicationLaunchFailed", "ApplictionExecutableNotFound", "ApplicationNotFound", diff --git a/pype/lib/avalon_context.py b/pype/lib/avalon_context.py index fd4155703e..a5728dba22 100644 --- a/pype/lib/avalon_context.py +++ b/pype/lib/avalon_context.py @@ -1119,3 +1119,30 @@ class BuildWorkfile: ) return output + + +def get_creator_by_name(creator_name, case_sensitive=False): + """Find creator plugin by name. + + Args: + creator_name (str): Name of creator class that should be returned. + case_sensitive (bool): Match of creator plugin name is case sensitive. + Set to `False` by default. + + Returns: + Creator: Return first matching plugin or `None`. + """ + # Lower input creator name if is not case sensitive + if not case_sensitive: + creator_name = creator_name.lower() + + for creator_plugin in avalon.api.discover(avalon.api.Creator): + _creator_name = creator_plugin.__name__ + + # Lower creator plugin name if is not case sensitive + if not case_sensitive: + _creator_name = _creator_name.lower() + + if _creator_name == creator_name: + return creator_plugin + return None From 8795da6ae59f27cab6997c1334aba220a99d8d89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Feb 2021 18:11:39 +0100 Subject: [PATCH 32/38] blender's load_layout is using get_creator_by_name to to be able create new instances --- .../hosts/blender/plugins/load/load_layout.py | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/pype/hosts/blender/plugins/load/load_layout.py b/pype/hosts/blender/plugins/load/load_layout.py index 6c1afafbee..8d4c9fb75c 100644 --- a/pype/hosts/blender/plugins/load/load_layout.py +++ b/pype/hosts/blender/plugins/load/load_layout.py @@ -12,6 +12,7 @@ from typing import Dict, List, Optional from avalon import api, blender, pipeline import bpy import pype.hosts.blender.api.plugin as plugin +from pype.lib import get_creator_by_name class BlendLayoutLoader(plugin.AssetLoader): @@ -24,6 +25,9 @@ class BlendLayoutLoader(plugin.AssetLoader): icon = "code-fork" color = "orange" + animation_creator_name = "CreateAnimation" + setdress_creator_name = "CreateSetDress" + def _remove(self, objects, obj_container): for obj in list(objects): if obj.type == 'ARMATURE': @@ -320,7 +324,7 @@ class UnrealLayoutLoader(plugin.AssetLoader): for c in bpy.data.collections: metadata = c.get('avalon') - if metadata: + if metadata: print("metadata.get('id')") print(metadata.get('id')) if metadata and metadata.get('id') == 'pyblish.avalon.instance': @@ -375,7 +379,7 @@ class UnrealLayoutLoader(plugin.AssetLoader): ) def _process( - self, libpath, layout_container, container_name, representation, + self, libpath, layout_container, container_name, representation, actions, parent ): with open(libpath, "r") as fp: @@ -428,6 +432,12 @@ class UnrealLayoutLoader(plugin.AssetLoader): objects_to_transform = [] + creator_plugin = get_creator_by_name(self.animation_creator_name) + if not creator_plugin: + raise ValueError("Creator plugin \"{}\" was not found.".format( + self.animation_creator_name + )) + if family == 'rig': for o in objects: if o.type == 'ARMATURE': @@ -436,9 +446,9 @@ class UnrealLayoutLoader(plugin.AssetLoader): o.select_set(True) asset = api.Session["AVALON_ASSET"] c = api.create( + creator_plugin, name="animation_" + element_collection.name, asset=asset, - family="animation", options={"useSelection": True}, data={"dependencies": representation}) scene.collection.children.unlink(c) @@ -505,15 +515,20 @@ class UnrealLayoutLoader(plugin.AssetLoader): # Create a setdress subset to contain all the animation for all # the rigs in the layout + creator_plugin = get_creator_by_name(self.setdress_creator_name) + if not creator_plugin: + raise ValueError("Creator plugin \"{}\" was not found.".format( + self.setdress_creator_name + )) parent = api.create( + creator_plugin, name="animation", asset=api.Session["AVALON_ASSET"], - family="setdress", options={"useSelection": True}, data={"dependencies": str(context["representation"]["_id"])}) layout_collection = self._process( - libpath, layout_container, container_name, + libpath, layout_container, container_name, str(context["representation"]["_id"]), None, parent) container_metadata["obj_container"] = layout_collection @@ -606,15 +621,21 @@ class UnrealLayoutLoader(plugin.AssetLoader): bpy.data.collections.remove(obj_container) + creator_plugin = get_creator_by_name(self.setdress_creator_name) + if not creator_plugin: + raise ValueError("Creator plugin \"{}\" was not found.".format( + self.setdress_creator_name + )) + parent = api.create( + creator_plugin, name="animation", asset=api.Session["AVALON_ASSET"], - family="setdress", options={"useSelection": True}, data={"dependencies": str(representation["_id"])}) layout_collection = self._process( - libpath, layout_container, container_name, + libpath, layout_container, container_name, str(representation["_id"]), actions, parent) layout_container_metadata["obj_container"] = layout_collection From 23bbca09c4362574f98f91ab95288e87fdefccf0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 4 Feb 2021 18:14:18 +0100 Subject: [PATCH 33/38] Maya's reference loader is using get_creator_by_name to be able create new instances --- pype/hosts/maya/plugins/load/load_reference.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pype/hosts/maya/plugins/load/load_reference.py b/pype/hosts/maya/plugins/load/load_reference.py index d37579423c..7ef6417b9f 100644 --- a/pype/hosts/maya/plugins/load/load_reference.py +++ b/pype/hosts/maya/plugins/load/load_reference.py @@ -3,6 +3,7 @@ from avalon import api, maya from maya import cmds import os from pype.api import get_project_settings +from pype.lib import get_creator_by_name class ReferenceLoader(pype.hosts.maya.api.plugin.ReferenceLoader): @@ -25,6 +26,9 @@ class ReferenceLoader(pype.hosts.maya.api.plugin.ReferenceLoader): icon = "code-fork" color = "orange" + # Name of creator class that will be used to create animation instance + animation_creator_name = "CreateAnimation" + def process_reference(self, context, name, namespace, options): import maya.cmds as cmds from avalon import maya @@ -135,10 +139,13 @@ class ReferenceLoader(pype.hosts.maya.api.plugin.ReferenceLoader): self.log.info("Creating subset: {}".format(namespace)) # Create the animation instance + creator_plugin = get_creator_by_name(self.animation_creator_name) with maya.maintained_selection(): cmds.select([output, controls] + roots, noExpand=True) - api.create(name=namespace, - asset=asset, - family="animation", - options={"useSelection": True}, - data={"dependencies": dependency}) + api.create( + creator_plugin, + name=namespace, + asset=asset, + options={"useSelection": True}, + data={"dependencies": dependency} + ) From 9a70742c61097edb36ad32ed1c3044150e7cc9fb Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 5 Feb 2021 10:40:55 +0100 Subject: [PATCH 34/38] fix pype_root directories for build and env --- tools/build.sh | 2 +- tools/create_env.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/build.sh b/tools/build.sh index a0cc398a35..0b97e8d8df 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -114,7 +114,7 @@ echo -e "${RST}" detect_python || return 1 # Directories -pype_root=$(dirname $(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))) +pype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) pushd "$pype_root" || return > /dev/null version_command="import os;exec(open(os.path.join('$pype_root', 'pype', 'version.py')).read());print(__version__);" diff --git a/tools/create_env.sh b/tools/create_env.sh index d053e740c2..5e9f21abcc 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -120,7 +120,7 @@ echo -e "${RST}" detect_python || return 1 # Directories -pype_root=$(dirname $(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))) +pype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) pushd "$pype_root" || return > /dev/null echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" From 761de7339dbcd65bfba136cb39673e0e710aacc5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 5 Feb 2021 14:52:24 +0100 Subject: [PATCH 35/38] do not skip entity changes if does not have any type specific custom attributes --- pype/modules/ftrack/events/event_sync_to_avalon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 0209dfd53a..9d7be605b3 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -1815,7 +1815,7 @@ class SyncToAvalonEvent(BaseEvent): # Ftrack's entity_type does not have defined custom attributes if ent_cust_attrs is None: - continue + ent_cust_attrs = [] for key, values in ent_info["changes"].items(): if key in hier_attrs_keys: From 98829009860031cd805cbac207c4631e1cf69420 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 5 Feb 2021 15:14:12 +0100 Subject: [PATCH 36/38] use configuration id instead of configuration key to query custom attribute values --- .../ftrack/events/event_sync_to_avalon.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 9d7be605b3..daab3425f3 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -1967,11 +1967,20 @@ class SyncToAvalonEvent(BaseEvent): cust_attrs, hier_attrs = self.avalon_cust_attrs # Hierarchical custom attributes preparation *** + hier_attr_key_by_id = { + attr["id"]: attr["key"] + for attr in hier_attrs + } + hier_attr_id_by_key = { + key: attr_id + for attr_id, key in hier_attr_key_by_id.items() + } + if all_keys: hier_cust_attrs_keys = [ - attr["key"] for attr in hier_attrs if ( - not attr["key"].startswith("avalon_") - ) + key + for key in hier_attr_id_by_key.keys() + if not key.startswith("avalon_") ] mongo_ftrack_mapping = {} @@ -2077,15 +2086,19 @@ class SyncToAvalonEvent(BaseEvent): entity_ids_joined = ", ".join([ "\"{}\"".format(id) for id in cust_attrs_ftrack_ids ]) + configuration_ids = set() + for key in hier_cust_attrs_keys: + configuration_ids.add(hier_attr_id_by_key[key]) + attributes_joined = ", ".join([ - "\"{}\"".format(name) for name in hier_cust_attrs_keys + "\"{}\"".format(conf_id) for conf_id in configuration_ids ]) queries = [{ "action": "query", "expression": ( "select value, entity_id from CustomAttributeValue " - "where entity_id in ({}) and configuration.key in ({})" + "where entity_id in ({}) and configuration_id in ({})" ).format(entity_ids_joined, attributes_joined) }] @@ -2110,7 +2123,7 @@ class SyncToAvalonEvent(BaseEvent): if value["value"] is None: continue entity_id = value["entity_id"] - key = value["configuration"]["key"] + key = hier_attr_key_by_id[value["configuration_id"]] entities_dict[entity_id]["hier_attrs"][key] = value["value"] # Get dictionary with not None hierarchical values to pull to childs From 1a8e925c20111dd082e7410d2b3a9db375ed6a16 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 5 Feb 2021 15:30:03 +0100 Subject: [PATCH 37/38] fix(nuke): missing preset's variable --- pype/hosts/nuke/plugins/publish/extract_thumbnail.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/hosts/nuke/plugins/publish/extract_thumbnail.py b/pype/hosts/nuke/plugins/publish/extract_thumbnail.py index a53cb4d146..fd3eaabe13 100644 --- a/pype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/pype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -18,6 +18,9 @@ class ExtractThumbnail(pype.api.Extractor): families = ["review"] hosts = ["nuke"] + # presets + nodes = {} + def process(self, instance): if "render.farm" in instance.data["families"]: return @@ -164,7 +167,8 @@ class ExtractThumbnail(pype.api.Extractor): if ipn_orig: nuke.nodeCopy('%clipboard%') - [n.setSelected(False) for n in nuke.selectedNodes()] # Deselect all + # Deselect all + [n.setSelected(False) for n in nuke.selectedNodes()] nuke.nodePaste('%clipboard%') From 6c8f0464942c49a80155639bbe213393e4131291 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 5 Feb 2021 15:53:01 +0100 Subject: [PATCH 38/38] only use existing attributes in alembic --- pype/hosts/maya/api/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pype/hosts/maya/api/plugin.py b/pype/hosts/maya/api/plugin.py index a5c57f1ab8..c7416920df 100644 --- a/pype/hosts/maya/api/plugin.py +++ b/pype/hosts/maya/api/plugin.py @@ -220,8 +220,7 @@ class ReferenceLoader(api.Loader): "{}:*".format(members[0].split(":")[0]), type="AlembicNode" ) if alembic_nodes: - for attr in alembic_attrs: - value = alembic_data[attr] + for attr, value in alembic_data.items(): cmds.setAttr("{}.{}".format(alembic_nodes[0], attr), value) # Fix PLN-40 for older containers created with Avalon that had the