From 49e166044563b9fdced37747fa69ef31f3986e09 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 17 Mar 2020 18:22:12 +0100 Subject: [PATCH 01/78] initial commit `PYPE_STUDIO_CORE_*` environment keys replaced with `PYPE_CORE_*` --- pype/plugins/global/publish/submit_publish_job.py | 4 ++-- pype/plugins/maya/publish/submit_maya_muster.py | 15 +++++++-------- pype/plugins/nuke/publish/submit_nuke_deadline.py | 9 ++++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 47c0272254..bc2a5384e6 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -21,8 +21,8 @@ def _get_script(): module_path = module_path[: -len(".pyc")] + ".py" module_path = os.path.normpath(module_path) - mount_root = os.path.normpath(os.environ["PYPE_STUDIO_CORE_MOUNT"]) - network_root = os.path.normpath(os.environ["PYPE_STUDIO_CORE_PATH"]) + mount_root = os.path.normpath(os.environ["PYPE_CORE_MOUNT"]) + network_root = os.path.normpath(os.environ["PYPE_CORE_PATH"]) module_path = module_path.replace(mount_root, network_root) diff --git a/pype/plugins/maya/publish/submit_maya_muster.py b/pype/plugins/maya/publish/submit_maya_muster.py index ac60c40bf7..af968ed773 100644 --- a/pype/plugins/maya/publish/submit_maya_muster.py +++ b/pype/plugins/maya/publish/submit_maya_muster.py @@ -311,12 +311,11 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): # replace path for UNC / network share paths, co PYPE is found # over network. It assumes PYPE is located somewhere in - # PYPE_STUDIO_CORE_PATH + # PYPE_CORE_PATH pype_root = os.environ["PYPE_ROOT"].replace( - os.path.normpath( - os.environ['PYPE_STUDIO_CORE_MOUNT']), # noqa - os.path.normpath( - os.environ['PYPE_STUDIO_CORE_PATH'])) # noqa + os.path.normpath(os.environ['PYPE_CORE_MOUNT']), + os.path.normpath(os.environ['PYPE_CORE_PATH']) + ) # we must provide either full path to executable or use musters own # python named MPython.exe, residing directly in muster bin @@ -521,7 +520,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): clean_path = "" self.log.debug("key: {}".format(key)) to_process = environment[key] - if key == "PYPE_STUDIO_CORE_MOUNT": + if key == "PYPE_CORE_MOUNT": clean_path = environment[key] elif "://" in environment[key]: clean_path = environment[key] @@ -542,8 +541,8 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): # this should replace paths so they are pointing to network share clean_path = clean_path.replace( - os.path.normpath(environment['PYPE_STUDIO_CORE_MOUNT']), - os.path.normpath(environment['PYPE_STUDIO_CORE_PATH'])) + os.path.normpath(environment['PYPE_CORE_MOUNT']), + os.path.normpath(environment['PYPE_CORE_PATH'])) clean_environment[key] = clean_path return clean_environment diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 0a9ef33398..9ee988b5ae 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -198,7 +198,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): clean_path = "" self.log.debug("key: {}".format(key)) to_process = environment[key] - if key == "PYPE_STUDIO_CORE_MOUNT": + if key == "PYPE_CORE_MOUNT": clean_path = environment[key] elif "://" in environment[key]: clean_path = environment[key] @@ -221,10 +221,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): 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 + os.path.normpath(environment['PYPE_CORE_MOUNT']), + os.path.normpath(environment['PYPE_CORE_PATH']) + ) clean_environment[key] = clean_path environment = clean_environment From f4df58986c195133b9e2d6cf77da78fc247a1579 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 18 Mar 2020 17:39:36 +0100 Subject: [PATCH 02/78] pype init sets root by current project --- pype/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/__init__.py b/pype/__init__.py index 5cd9832558..34d2d90649 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -3,7 +3,7 @@ import os from pyblish import api as pyblish from avalon import api as avalon from .lib import filter_pyblish_plugins -from pypeapp import config +from pypeapp import config, Roots import logging @@ -85,6 +85,10 @@ def install(): avalon.register_plugin_path(avalon.Loader, plugin_path) avalon.register_plugin_path(avalon.Creator, plugin_path) + if project_name: + root_obj = Roots(project_name) + root = root_obj.roots + avalon.register_root(root) # apply monkey patched discover to original one avalon.discover = patched_discover From ee99289ff31d5339405b867d5cc7c9abf1bf373d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Mar 2020 15:30:27 +0100 Subject: [PATCH 03/78] formatting changes --- pype/ftrack/actions/action_create_folders.py | 123 ++++++------------- 1 file changed, 37 insertions(+), 86 deletions(-) diff --git a/pype/ftrack/actions/action_create_folders.py b/pype/ftrack/actions/action_create_folders.py index 68cf837469..80618e67e8 100644 --- a/pype/ftrack/actions/action_create_folders.py +++ b/pype/ftrack/actions/action_create_folders.py @@ -12,9 +12,6 @@ from pypeapp import config, Anatomy class CreateFolders(BaseAction): - - '''Custom action.''' - #: Action identifier. identifier = 'create.folders' @@ -29,75 +26,69 @@ class CreateFolders(BaseAction): db = DbConnector() def discover(self, session, entities, event): - ''' Validation ''' if len(entities) != 1: return False - not_allowed = ['assetversion', 'project'] + not_allowed = ["assetversion", "project"] if entities[0].entity_type.lower() in not_allowed: return False return True def interface(self, session, entities, event): - if event['data'].get('values', {}): + if event["data"].get("values", {}): return entity = entities[0] without_interface = True - for child in entity['children']: - if child['object_type']['name'].lower() != 'task': + for child in entity["children"]: + if child["object_type"]["name"].lower() != "task": without_interface = False break self.without_interface = without_interface if without_interface: return - title = 'Create folders' + title = "Create folders" - entity_name = entity['name'] + entity_name = entity["name"] msg = ( - '

Do you want create folders also' - ' for all children of "{}"?

' + "

Do you want create folders also" + " for all children of \"{}\"?

" ) - if entity.entity_type.lower() == 'project': - entity_name = entity['full_name'] - msg = msg.replace(' also', '') - msg += '

(Project root won\'t be created if not checked)

' + if entity.entity_type.lower() == "project": + entity_name = entity["full_name"] + msg = msg.replace(" also", "") + msg += "

(Project root won't be created if not checked)

" items = [] item_msg = { - 'type': 'label', - 'value': msg.format(entity_name) + "type": "label", + "value": msg.format(entity_name) } item_label = { - 'type': 'label', - 'value': 'With all chilren entities' + "type": "label", + "value": "With all chilren entities" } item = { - 'name': 'children_included', - 'type': 'boolean', - 'value': False + "name": "children_included", + "type": "boolean", + "value": False } items.append(item_msg) items.append(item_label) items.append(item) - if len(items) == 0: - return { - 'success': False, - 'message': 'Didn\'t found any running jobs' - } - else: - return { - 'items': items, - 'title': title - } + return { + "items": items, + "title": title + } def launch(self, session, entities, event): '''Callback method for custom action.''' with_childrens = True if self.without_interface is False: - if 'values' not in event['data']: + if "values" not in event["data"]: return - with_childrens = event['data']['values']['children_included'] + with_childrens = event["data"]["values"]["children_included"] + entity = entities[0] if entity.entity_type.lower() == 'project': proj = entity @@ -105,6 +96,7 @@ class CreateFolders(BaseAction): proj = entity['project'] project_name = proj['full_name'] project_code = proj['name'] + if entity.entity_type.lower() == 'project' and with_childrens == False: return { 'success': True, @@ -136,21 +128,20 @@ class CreateFolders(BaseAction): template_publish = templates["avalon"]["publish"] collected_paths = [] - presets = config.get_presets()['tools']['sw_folders'] + presets = config.get_presets()["tools"]["sw_folders"] for entity in all_entities: - if entity.entity_type.lower() == 'project': + if entity.entity_type.lower() == "project": continue ent_data = data.copy() - asset_name = entity['name'] - ent_data['asset'] = asset_name + ent_data["asset"] = entity["name"] - parents = entity['link'] - hierarchy_names = [p['name'] for p in parents[1:-1]] - hierarchy = '' + parents = entity["link"][1:-1] + hierarchy_names = [p["name"] for p in parents] + hierarchy = "" if hierarchy_names: hierarchy = os.path.sep.join(hierarchy_names) - ent_data['hierarchy'] = hierarchy + ent_data["hierarchy"] = hierarchy tasks_created = False if entity['children']: @@ -222,8 +213,8 @@ class CreateFolders(BaseAction): os.makedirs(path) return { - 'success': True, - 'message': 'Created Folders Successfully!' + "success": True, + "message": "Successfully created project folders." } def get_notask_children(self, entity): @@ -325,45 +316,5 @@ class PartialDict(dict): def register(session, plugins_presets={}): - '''Register plugin. Called when used as an plugin.''' - + """Register plugin. Called when used as an plugin.""" CreateFolders(session, plugins_presets).register() - - -def main(arguments=None): - '''Set up logging and register action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - # Set up basic logging - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - session = ftrack_api.Session() - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) From 639896f4a8aedfe75f74a99f2a382a337626da48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 20 Mar 2020 16:37:16 +0100 Subject: [PATCH 04/78] app launcher uses roots --- pype/ftrack/lib/ftrack_app_handler.py | 275 +++++++++++--------------- 1 file changed, 118 insertions(+), 157 deletions(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index eebffda280..aa57672f09 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -1,14 +1,14 @@ import os import sys +import copy import platform from avalon import lib as avalonlib import acre -from pype import api as pype from pype import lib as pypelib from pypeapp import config from .ftrack_base_handler import BaseHandler -from pypeapp import Anatomy +from pypeapp import Anatomy, Roots class AppAction(BaseHandler): @@ -157,209 +157,176 @@ class AppAction(BaseHandler): ''' entity = entities[0] - project_name = entity['project']['full_name'] + project_name = entity["project"]["full_name"] database = pypelib.get_avalon_database() - # Get current environments - env_list = [ - 'AVALON_PROJECT', - 'AVALON_SILO', - 'AVALON_ASSET', - 'AVALON_TASK', - 'AVALON_APP', - 'AVALON_APP_NAME' - ] - env_origin = {} - for env in env_list: - env_origin[env] = os.environ.get(env, None) - - # set environments for Avalon - os.environ["AVALON_PROJECT"] = project_name - os.environ["AVALON_SILO"] = entity['ancestors'][0]['name'] - os.environ["AVALON_ASSET"] = entity['parent']['name'] - os.environ["AVALON_TASK"] = entity['name'] - os.environ["AVALON_APP"] = self.identifier.split("_")[0] - os.environ["AVALON_APP_NAME"] = self.identifier - - anatomy = Anatomy() + asset_name = entity["parent"]["name"] + asset_document = database[project_name].find_one({ + "type": "asset", + "name": asset_name + }) hierarchy = "" - parents = database[project_name].find_one({ - "type": 'asset', - "name": entity['parent']['name'] - })['data']['parents'] - - if parents: - hierarchy = os.path.join(*parents) - - os.environ["AVALON_HIERARCHY"] = hierarchy - - application = avalonlib.get_application(os.environ["AVALON_APP_NAME"]) + asset_doc_parents = asset_document["data"].get("parents") + if len(asset_doc_parents) > 0: + hierarchy = os.path.join(*asset_doc_parents) + application = avalonlib.get_application(self.identifier) data = { - "root": os.environ.get("PYPE_STUDIO_PROJECTS_MOUNT"), + "root": Roots(project_name).roots, "project": { - "name": entity['project']['full_name'], - "code": entity['project']['name'] + "name": entity["project"]["full_name"], + "code": entity["project"]["name"] }, - "task": entity['name'], - "asset": entity['parent']['name'], + "task": entity["name"], + "asset": asset_name, "app": application["application_dir"], - "hierarchy": hierarchy, + "hierarchy": hierarchy } - av_project = database[project_name].find_one({"type": 'project'}) - templates = None - if av_project: - work_template = av_project.get('config', {}).get('template', {}).get( - 'work', None - ) - work_template = None try: - work_template = work_template.format(**data) - except Exception: - try: - anatomy = anatomy.format(data) - work_template = anatomy["work"]["folder"] + anatomy = Anatomy(project_name) + anatomy_filled = anatomy.format(data) + workdir = os.path.normpath(anatomy_filled["work"]["folder"]) - except Exception as exc: - msg = "{} Error in anatomy.format: {}".format( - __name__, str(exc) - ) - self.log.error(msg, exc_info=True) - return { - 'success': False, - 'message': msg - } + except Exception as exc: + msg = "Error in anatomy.format: {}".format( + str(exc) + ) + self.log.error(msg, exc_info=True) + return { + "success": False, + "message": msg + } - workdir = os.path.normpath(work_template) - os.environ["AVALON_WORKDIR"] = workdir try: os.makedirs(workdir) except FileExistsError: pass + # set environments for Avalon + prep_env = copy.deepcopy(os.environ) + prep_env.update({ + "AVALON_PROJECT": project_name, + "AVALON_ASSET": asset_name, + "AVALON_TASK": entity["name"], + "AVALON_APP": self.identifier.split("_")[0], + "AVALON_APP_NAME": self.identifier, + "AVALON_HIERARCHY": hierarchy, + "AVALON_WORKDIR": workdir + }) + # collect all parents from the task parents = [] for item in entity['link']: parents.append(session.get(item['type'], item['id'])) # collect all the 'environment' attributes from parents - tools_attr = [os.environ["AVALON_APP"], os.environ["AVALON_APP_NAME"]] - for parent in reversed(parents): - # check if the attribute is empty, if not use it - if parent['custom_attributes']['tools_env']: - tools_attr.extend(parent['custom_attributes']['tools_env']) - break + tools_attr = [prep_env["AVALON_APP"], prep_env["AVALON_APP_NAME"]] + tools_env = asset_document["data"].get("tools_env") or [] + tools_attr.extend(tools_env) tools_env = acre.get_tools(tools_attr) env = acre.compute(tools_env) - env = acre.merge(env, current_env=dict(os.environ)) - env = acre.append(dict(os.environ), env) - - - # - # tools_env = acre.get_tools(tools) - # env = acre.compute(dict(tools_env)) - # env = acre.merge(env, dict(os.environ)) - # os.environ = acre.append(dict(os.environ), env) - # os.environ = acre.compute(os.environ) + env = acre.merge(env, current_env=dict(prep_env)) + env = acre.append(dict(prep_env), env) # Get path to execute - st_temp_path = os.environ['PYPE_CONFIG'] + st_temp_path = os.environ["PYPE_CONFIG"] os_plat = platform.system().lower() # Path to folder with launchers - path = os.path.join(st_temp_path, 'launchers', os_plat) + path = os.path.join(st_temp_path, "launchers", os_plat) + # Full path to executable launcher execfile = None - if sys.platform == "win32": - for ext in os.environ["PATHEXT"].split(os.pathsep): fpath = os.path.join(path.strip('"'), self.executable + ext) if os.path.isfile(fpath) and os.access(fpath, os.X_OK): execfile = fpath break - pass # Run SW if was found executable - if execfile is not None: - popen = avalonlib.launch( - executable=execfile, args=[], environment=env - ) - else: + if execfile is None: return { - 'success': False, - 'message': "We didn't found launcher for {0}" - .format(self.label) - } - pass - - if sys.platform.startswith('linux'): - execfile = os.path.join(path.strip('"'), self.executable) - if os.path.isfile(execfile): - try: - fp = open(execfile) - except PermissionError as p: - self.log.exception('Access denied on {0} - {1}'.format( - execfile, p)) - return { - 'success': False, - 'message': "Access denied on launcher - {}".format( - execfile) - } - fp.close() - # check executable permission - if not os.access(execfile, os.X_OK): - self.log.error('No executable permission on {}'.format( - execfile)) - return { - 'success': False, - 'message': "No executable permission - {}".format( - execfile) - } - pass - else: - self.log.error('Launcher doesn\'t exist - {}'.format( - execfile)) - return { - 'success': False, - 'message': "Launcher doesn't exist - {}".format(execfile) + "success": False, + "message": "We didn't found launcher for {0}".format( + self.label + ) } - pass - # Run SW if was found executable - if execfile is not None: - avalonlib.launch( - '/usr/bin/env', args=['bash', execfile], environment=env - ) - else: + + popen = avalonlib.launch( + executable=execfile, args=[], environment=env + ) + + elif sys.platform.startswith("linux"): + execfile = os.path.join(path.strip('"'), self.executable) + if not os.path.isfile(execfile): + msg = "Launcher doesn't exist - {}".format(execfile) + + self.log.error(msg) return { - 'success': False, - 'message': "We didn't found launcher for {0}" - .format(self.label) - } - pass + "success": False, + "message": msg + } + + try: + fp = open(execfile) + except PermissionError as perm_exc: + msg = "Access denied on launcher {} - {}".format( + execfile, perm_exc + ) + + self.log.exception(msg, exc_info=True) + return { + "success": False, + "message": msg + } + + fp.close() + # check executable permission + if not os.access(execfile, os.X_OK): + msg = "No executable permission - {}".format(execfile) + + self.log.error(msg) + return { + "success": False, + "message": msg + } + + # Run SW if was found executable + if execfile is None: + return { + "success": False, + "message": "We didn't found launcher for {0}".format( + self.label + ) + } + + popen = avalonlib.launch( + "/usr/bin/env", args=["bash", execfile], environment=env + ) # Change status of task to In progress presets = config.get_presets()["ftrack"]["ftrack_config"] - if 'status_update' in presets: - statuses = presets['status_update'] + if "status_update" in presets: + statuses = presets["status_update"] - actual_status = entity['status']['name'].lower() + actual_status = entity["status"]["name"].lower() already_tested = [] ent_path = "/".join( - [ent["name"] for ent in entity['link']] + [ent["name"] for ent in entity["link"]] ) while True: next_status_name = None for key, value in statuses.items(): if key in already_tested: continue - if actual_status in value or '_any_' in value: - if key != '_ignore_': + if actual_status in value or "_any_" in value: + if key != "_ignore_": next_status_name = key already_tested.append(key) break @@ -369,12 +336,12 @@ class AppAction(BaseHandler): break try: - query = 'Status where name is "{}"'.format( + query = "Status where name is \"{}\"".format( next_status_name ) status = session.query(query).one() - entity['status'] = status + entity["status"] = status session.commit() self.log.debug("Changing status to \"{}\" <{}>".format( next_status_name, ent_path @@ -384,18 +351,12 @@ class AppAction(BaseHandler): except Exception: session.rollback() msg = ( - 'Status "{}" in presets wasn\'t found' - ' on Ftrack entity type "{}"' + "Status \"{}\" in presets wasn't found" + " on Ftrack entity type \"{}\"" ).format(next_status_name, entity.entity_type) self.log.warning(msg) - # Set origin avalon environments - for key, value in env_origin.items(): - if value == None: - value = "" - os.environ[key] = value - return { - 'success': True, - 'message': "Launching {0}".format(self.label) + "success": True, + "message": "Launching {0}".format(self.label) } From fe7a6776214db3becc119d6f28930d10e22ba5c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Mar 2020 16:27:18 +0100 Subject: [PATCH 05/78] template_data store same frame as was used in anatomy filling --- 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 7a346f6888..b1ec8b0022 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -325,6 +325,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): test_dest_files.append( os.path.normpath(template_filled) ) + template_data["frame"] = repre_context["frame"] self.log.debug( "test_dest_files: {}".format(str(test_dest_files))) From d9686af193304b5bf2068fd889ef97b00ef5b9cc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Mar 2020 16:27:53 +0100 Subject: [PATCH 06/78] template_name is 100% per representation now --- pype/plugins/global/publish/integrate_new.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index b1ec8b0022..6c36b00fa8 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -88,6 +88,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "project", "asset", "task", "subset", "version", "representation", "family", "hierarchy", "task", "username" ] + default_template_name = "publish" def process(self, instance): @@ -260,7 +261,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): # Each should be a single representation (as such, a single extension) representations = [] destination_list = [] - template_name = 'publish' + if 'transfers' not in instance.data: instance.data['transfers'] = [] @@ -287,8 +288,10 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): files = repre['files'] if repre.get('stagingDir'): stagingdir = repre['stagingDir'] - if repre.get('anatomy_template'): - template_name = repre['anatomy_template'] + + template_name = ( + repre.get('anatomy_template') or self.default_template_name + ) if repre.get("outputName"): template_data["output"] = repre['outputName'] From b6c25f90c79574b4ce75b696e1f2a6c3916841a1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 25 Mar 2020 16:58:26 +0100 Subject: [PATCH 07/78] unc converting removed from integrators since PYPE_STUDIO_PROJECTS_MOUNT and PYPE_STUDIO_PROJECTS_PATH does not exist --- .../publish/integrate_master_version.py | 72 ------------------- pype/plugins/global/publish/integrate_new.py | 26 +------ 2 files changed, 2 insertions(+), 96 deletions(-) diff --git a/pype/plugins/global/publish/integrate_master_version.py b/pype/plugins/global/publish/integrate_master_version.py index 3c7838b708..8a74f5f86a 100644 --- a/pype/plugins/global/publish/integrate_master_version.py +++ b/pype/plugins/global/publish/integrate_master_version.py @@ -481,9 +481,6 @@ class IntegrateMasterVersion(pyblish.api.InstancePlugin): def copy_file(self, src_path, dst_path): # TODO check drives if are the same to check if cas hardlink - dst_path = self.path_root_check(dst_path) - src_path = self.path_root_check(src_path) - dirname = os.path.dirname(dst_path) try: @@ -513,75 +510,6 @@ class IntegrateMasterVersion(pyblish.api.InstancePlugin): shutil.copy(src_path, dst_path) - def path_root_check(self, path): - normalized_path = os.path.normpath(path) - forward_slash_path = normalized_path.replace("\\", "/") - - drive, _path = os.path.splitdrive(normalized_path) - if os.path.exists(drive + "/"): - key = "drive_check{}".format(drive) - if key not in self.path_checks: - self.log.debug( - "Drive \"{}\" exist. Nothing to change.".format(drive) - ) - self.path_checks.append(key) - - return normalized_path - - path_env_key = "PYPE_STUDIO_PROJECTS_PATH" - mount_env_key = "PYPE_STUDIO_PROJECTS_MOUNT" - missing_envs = [] - if path_env_key not in os.environ: - missing_envs.append(path_env_key) - - if mount_env_key not in os.environ: - missing_envs.append(mount_env_key) - - if missing_envs: - key = "missing_envs" - if key not in self.path_checks: - self.path_checks.append(key) - _add_s = "" - if len(missing_envs) > 1: - _add_s = "s" - - self.log.warning(( - "Can't replace MOUNT drive path to UNC path due to missing" - " environment variable{}: `{}`. This may cause issues" - " during publishing process." - ).format(_add_s, ", ".join(missing_envs))) - - return normalized_path - - unc_root = os.environ[path_env_key].replace("\\", "/") - mount_root = os.environ[mount_env_key].replace("\\", "/") - - # --- Remove slashes at the end of mount and unc roots --- - while unc_root.endswith("/"): - unc_root = unc_root[:-1] - - while mount_root.endswith("/"): - mount_root = mount_root[:-1] - # --- - - if forward_slash_path.startswith(unc_root): - self.log.debug(( - "Path already starts with UNC root: \"{}\"" - ).format(unc_root)) - return normalized_path - - if not forward_slash_path.startswith(mount_root): - self.log.warning(( - "Path do not start with MOUNT root \"{}\" " - "set in environment variable \"{}\"" - ).format(unc_root, mount_env_key)) - return normalized_path - - # Replace Mount root with Unc root - path = unc_root + forward_slash_path[len(mount_root):] - - return os.path.normpath(path) - def version_from_representations(self, repres): for repre in repres: version = io.find_one({"_id": repre["parent"]}) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 6c36b00fa8..23bb4f1b66 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -390,7 +390,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): dst_start_frame, dst_tail ).replace("..", ".") - repre['published_path'] = self.unc_convert(dst) + repre['published_path'] = dst else: # Single file @@ -418,7 +418,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): instance.data["transfers"].append([src, dst]) published_files.append(dst) - repre['published_path'] = self.unc_convert(dst) + repre['published_path'] = dst self.log.debug("__ dst: {}".format(dst)) repre["publishedFiles"] = published_files @@ -522,23 +522,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.debug("Hardlinking file .. {} -> {}".format(src, dest)) self.hardlink_file(src, dest) - def unc_convert(self, path): - self.log.debug("> __ path: `{}`".format(path)) - drive, _path = os.path.splitdrive(path) - self.log.debug("> __ drive, _path: `{}`, `{}`".format(drive, _path)) - - if not os.path.exists(drive + "/"): - self.log.info("Converting to unc from environments ..") - - path_replace = os.getenv("PYPE_STUDIO_PROJECTS_PATH") - path_mount = os.getenv("PYPE_STUDIO_PROJECTS_MOUNT") - - if "/" in path_mount: - path = path.replace(path_mount[0:-1], path_replace) - else: - path = path.replace(path_mount, path_replace) - return path - def copy_file(self, src, dst): """ Copy given source to destination @@ -548,8 +531,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): Returns: None """ - src = self.unc_convert(src) - dst = self.unc_convert(dst) src = os.path.normpath(src) dst = os.path.normpath(dst) self.log.debug("Copying file .. {} -> {}".format(src, dst)) @@ -572,9 +553,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): def hardlink_file(self, src, dst): dirname = os.path.dirname(dst) - src = self.unc_convert(src) - dst = self.unc_convert(dst) - try: os.makedirs(dirname) except OSError as e: From 1f2d1a55dcf9c29003cf50bc9f1b9697448de146 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Mar 2020 18:59:25 +0100 Subject: [PATCH 08/78] few minor changes --- pype/ftrack/lib/ftrack_app_handler.py | 7 +++---- pype/nuke/lib.py | 6 ++---- pype/plugins/global/publish/collect_anatomy.py | 2 -- pype/plugins/global/publish/submit_publish_job.py | 4 +--- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index aa57672f09..d36ed9c479 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -8,7 +8,7 @@ from pype import lib as pypelib from pypeapp import config from .ftrack_base_handler import BaseHandler -from pypeapp import Anatomy, Roots +from pypeapp import Anatomy class AppAction(BaseHandler): @@ -89,8 +89,8 @@ class AppAction(BaseHandler): ''' if ( - len(entities) != 1 or - entities[0].entity_type.lower() != 'task' + len(entities) != 1 + or entities[0].entity_type.lower() != 'task' ): return False @@ -174,7 +174,6 @@ class AppAction(BaseHandler): application = avalonlib.get_application(self.identifier) data = { - "root": Roots(project_name).roots, "project": { "name": entity["project"]["full_name"], "code": entity["project"]["name"] diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index ad2d576da3..0eb6eaf282 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -192,7 +192,6 @@ def format_anatomy(data): data["version"] = pype.get_version_from_path(file) project_document = pype.get_project() data.update({ - "root": api.Session["AVALON_PROJECTS"], "subset": data["avalon"]["subset"], "asset": data["avalon"]["asset"], "task": api.Session["AVALON_TASK"], @@ -1092,7 +1091,6 @@ class BuildWorkfile(WorkfileSettings): self.to_script = to_script # collect data for formating self.data_tmp = { - "root": root_path or api.Session["AVALON_PROJECTS"], "project": {"name": self._project["name"], "code": self._project["data"].get("code", '')}, "asset": self._asset or os.environ["AVALON_ASSET"], @@ -1109,8 +1107,8 @@ class BuildWorkfile(WorkfileSettings): anatomy_filled = anatomy.format(self.data_tmp) # get dir and file for workfile - self.work_dir = anatomy_filled["avalon"]["work"] - self.work_file = anatomy_filled["avalon"]["workfile"] + ".nk" + self.work_dir = anatomy_filled["work"]["folder"] + self.work_file = anatomy_filled["work"]["path"] + ".nk" def save_script_as(self, path=None): # first clear anything in open window diff --git a/pype/plugins/global/publish/collect_anatomy.py b/pype/plugins/global/publish/collect_anatomy.py index 73ae3bb024..7fd2056213 100644 --- a/pype/plugins/global/publish/collect_anatomy.py +++ b/pype/plugins/global/publish/collect_anatomy.py @@ -26,7 +26,6 @@ class CollectAnatomy(pyblish.api.ContextPlugin): label = "Collect Anatomy" def process(self, context): - root_path = api.registered_root() task_name = api.Session["AVALON_TASK"] project_entity = context.data["projectEntity"] @@ -45,7 +44,6 @@ class CollectAnatomy(pyblish.api.ContextPlugin): hierarchy = os.path.join(*hierarchy_items) context_data = { - "root": root_path, "project": { "name": project_name, "code": project_entity["data"].get("code") diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index ecf8555c57..8b43bec544 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -152,9 +152,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_API_KEY", "FTRACK_SERVER", "PYPE_ROOT", - "PYPE_METADATA_FILE", - "PYPE_STUDIO_PROJECTS_PATH", - "PYPE_STUDIO_PROJECTS_MOUNT", + "PYPE_METADATA_FILE" ] # pool used to do the publishing job From 9cc0b993381329c087126f22b2a7f10b5b0dff19 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 12:17:28 +0200 Subject: [PATCH 09/78] submit to deadline sends project, metadatafile is with `{root}` key in value and deadline has `mount` path in `OutputDirectory0` --- .../global/publish/collect_rendered_files.py | 17 +++++++++- .../global/publish/submit_publish_job.py | 33 ++++++++++++++----- pype/scripts/publish_filesequence.py | 7 ---- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 552fd49f6d..35d875dc02 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -4,7 +4,7 @@ import json import pyblish.api from avalon import api -from pypeapp import PypeLauncher +from pypeapp import PypeLauncher, Roots class CollectRenderedFiles(pyblish.api.ContextPlugin): @@ -82,8 +82,23 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): "Missing `PYPE_PUBLISH_DATA`") paths = os.environ["PYPE_PUBLISH_DATA"].split(os.pathsep) + project_name = os.environ.get("AVALON_PROJECT") + if project_name is None: + root = None + self.log.warning( + "Environment `AVLAON_PROJECT` was not found." + "Could not set `root` which may cause issues." + ) + else: + self.log.info("Getting root setting for project \"{}\"".format( + project_name + )) + root = {"root": Roots(project_name)} + session_set = False for path in paths: + if root: + path = path.format(**root) data = self._load_json(path) if not session_set: self.log.info("Setting session using data from file") diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 8b43bec544..82bd0c5a78 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -185,15 +185,26 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): batch=job["Props"]["Name"], subset=subset ) - metadata_filename = "{}_metadata.json".format(subset) output_dir = instance.data["outputDir"] - metadata_path = os.path.join(output_dir, metadata_filename) - - metadata_path = os.path.normpath(metadata_path) - mount_root = os.path.normpath(os.environ["PYPE_STUDIO_PROJECTS_MOUNT"]) - network_root = os.environ["PYPE_STUDIO_PROJECTS_PATH"] - metadata_path = metadata_path.replace(mount_root, network_root) - metadata_path = os.path.normpath(metadata_path) + # Convert output dir to `{root}/rest/of/path/...` with Anatomy + anatomy_obj = instance.context.data["anatomy"] + root_name = anatomy_obj.templates["work"].get("root_name") + success, rootless_path = ( + anatomy_obj.roots.find_root_template_from_path( + output_dir, root_name + ) + ) + if not success: + # `rootless_path` is not set to `output_dir` if none of roots match + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(output_dirt)) + rootless_path = output_dir + else: + # If root was found then use `mount` root for `output_dir` + anatomy_obj.roots._root_key = "mount" + output_dir = rootless_path.format(**{"root": anatomy_obj.roots}) # Generate the payload for Deadline submission payload = { @@ -221,8 +232,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # Transfer the environment from the original job to this dependent # job so they use the same environment + metadata_filename = "{}_metadata.json".format(subset) + metadata_path = os.path.join(rootless_path, metadata_filename) + "TODO metadata_path replace root with {root[root_name]} + environment = job["Props"].get("Env", {}) environment["PYPE_METADATA_FILE"] = metadata_path + environment["AVALON_PROJECT"] = pyblish.api.Session["AVALON_PROJECT"] + i = 0 for index, key in enumerate(environment): if key.upper() in self.enviro_filter: diff --git a/pype/scripts/publish_filesequence.py b/pype/scripts/publish_filesequence.py index fe795564a5..8e14b62306 100644 --- a/pype/scripts/publish_filesequence.py +++ b/pype/scripts/publish_filesequence.py @@ -89,13 +89,6 @@ def __main__(): print("Paths: {}".format(kwargs.paths or [os.getcwd()])) paths = kwargs.paths or [os.environ.get("PYPE_METADATA_FILE")] or [os.getcwd()] # noqa - - for path in paths: - data = _load_json(path) - log.info("Setting session using data from file") - os.environ["AVALON_PROJECT"] = data["session"]["AVALON_PROJECT"] - break - args = [ os.path.join(pype_root, pype_command), "publish", From be8f1bd0f54d24aa850e0cd3e63076be948c04c3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 13:05:47 +0200 Subject: [PATCH 10/78] integrate new can find right source --- pype/plugins/global/publish/integrate_new.py | 46 ++++++++++++------- .../global/publish/submit_publish_job.py | 2 +- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 23bb4f1b66..39c47fa03b 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -639,24 +639,38 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): self.log.debug("Registered root: {}".format(api.registered_root())) # create relative source path for DB - try: - source = instance.data['source'] - except KeyError: - source = context.data["currentFile"] - source = source.replace(os.getenv("PYPE_STUDIO_PROJECTS_MOUNT"), - api.registered_root()) - relative_path = os.path.relpath(source, api.registered_root()) - source = os.path.join("{root}", relative_path).replace("\\", "/") + if "source" in instance.data: + source = instance.data["source"] + else: + current_file = context.data["currentFile"] + anatomy = instance.context.data["anatomy"] + root_name = anatomy.templates["work"].get("root_name") + success, rootless_path = ( + anatomy.roots.find_root_template_from_path( + current_file, root_name, others_on_fail=True + ) + ) + if not success: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(current_file)) + source = current_file + else: + source = rootless_path self.log.debug("Source: {}".format(source)) - version_data = {"families": families, - "time": context.data["time"], - "author": context.data["user"], - "source": source, - "comment": context.data.get("comment"), - "machine": context.data.get("machine"), - "fps": context.data.get( - "fps", instance.data.get("fps"))} + version_data = { + "families": families, + "time": context.data["time"], + "author": context.data["user"], + "source": source, + "comment": context.data.get("comment"), + "machine": context.data.get("machine"), + "fps": context.data.get( + "fps", instance.data.get("fps") + ) + } intent_value = instance.context.data.get("intent") if intent_value and isinstance(intent_value, dict): diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 82bd0c5a78..423ae01b79 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -203,7 +203,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): rootless_path = output_dir else: # If root was found then use `mount` root for `output_dir` - anatomy_obj.roots._root_key = "mount" + anatomy_obj.roots._root_type = "mount" output_dir = rootless_path.format(**{"root": anatomy_obj.roots}) # Generate the payload for Deadline submission From ef0239fc4f5e1252abf5d0a04842002a7b5f0b98 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 14:09:01 +0200 Subject: [PATCH 11/78] user assignment permissions event changed --- pype/ftrack/events/event_user_assigment.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/pype/ftrack/events/event_user_assigment.py b/pype/ftrack/events/event_user_assigment.py index eaacfd959a..a899efbe50 100644 --- a/pype/ftrack/events/event_user_assigment.py +++ b/pype/ftrack/events/event_user_assigment.py @@ -158,20 +158,10 @@ class UserAssigmentEvent(BaseEvent): """ project_name = task['project']['full_name'] project_code = task['project']['name'] - try: - root = os.environ['PYPE_STUDIO_PROJECTS_PATH'] - except KeyError: - msg = 'Project ({}) root not set'.format(project_name) - self.log.error(msg) - return { - 'success': False, - 'message': msg - } # fill in template data asset = self._get_asset(task) t_data = { - 'root': root, 'project': { 'name': project_name, 'code': project_code @@ -204,11 +194,12 @@ class UserAssigmentEvent(BaseEvent): data = self._get_template_data(task) # format directories to pass to shell script anatomy = Anatomy(data["project"]["name"]) + anatomy_filled = anatomy.format(data) # formatting work dir is easiest part as we can use whole path - work_dir = anatomy.format(data)['avalon']['work'] + work_dir = anatomy_filled["work"]["folder"] # we also need publish but not whole - filled_all = anatomy.format_all(data) - publish = filled_all['avalon']['publish'] + anatomy_filled.strict = False + publish = anatomy_filled["publosh"]["folder"] # now find path to {asset} m = re.search("(^.+?{})".format(data['asset']), From 03c1285f2d4a7d1cbf05ba4a55ff387718977505 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 14:14:08 +0200 Subject: [PATCH 12/78] source root should work --- pype/plugins/global/publish/integrate_new.py | 14 ++++---- .../global/publish/submit_publish_job.py | 32 +++++++++++++------ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 39c47fa03b..c8fa5b4074 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -637,27 +637,25 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): families.append(instance_family) families += current_families - self.log.debug("Registered root: {}".format(api.registered_root())) # create relative source path for DB if "source" in instance.data: source = instance.data["source"] else: - current_file = context.data["currentFile"] + source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( anatomy.roots.find_root_template_from_path( - current_file, root_name, others_on_fail=True + source, root_name, others_on_fail=True ) ) - if not success: + if success: + source = rootless_path + else: self.log.warning(( "Could not find root path for remapping \"{}\"." " This may cause issues on farm." - ).format(current_file)) - source = current_file - else: - source = rootless_path + ).format(source)) self.log.debug("Source: {}".format(source)) version_data = { diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 423ae01b79..f121628e01 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -187,10 +187,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): output_dir = instance.data["outputDir"] # Convert output dir to `{root}/rest/of/path/...` with Anatomy - anatomy_obj = instance.context.data["anatomy"] - root_name = anatomy_obj.templates["work"].get("root_name") + anatomy = instance.context.data["anatomy"] + root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( - anatomy_obj.roots.find_root_template_from_path( + anatomy.roots.find_root_template_from_path( output_dir, root_name ) ) @@ -203,8 +203,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): rootless_path = output_dir else: # If root was found then use `mount` root for `output_dir` - anatomy_obj.roots._root_type = "mount" - output_dir = rootless_path.format(**{"root": anatomy_obj.roots}) + anatomy.roots._root_type = "mount" + output_dir = rootless_path.format(**{"root": anatomy.roots}) # Generate the payload for Deadline submission payload = { @@ -572,11 +572,25 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): except KeyError: source = context.data["currentFile"] - source = source.replace( - os.getenv("PYPE_STUDIO_PROJECTS_MOUNT"), api.registered_root() + anatomy = context.data["anatomy"] + root_name = anatomy.templates["work"].get("root_name") + success, rootless_path = ( + anatomy.roots.find_root_template_from_path( + source, root_name + ) ) - relative_path = os.path.relpath(source, api.registered_root()) - source = os.path.join("{root}", relative_path).replace("\\", "/") + if success: + orig_root_type = anatomy.roots._root_type + anatomy.roots._root_type = "mount" + source = rootless_path.format(**{"root": anatomy.roots}) + anatomy.roots._root_type = orig_root_type + + else: + # `rootless_path` is not set to `source` if none of roots match + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(output_dirt)) families = ["render"] From 50bd855fb1e64c04c1642104fe20281bd2e208b3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 14:17:14 +0200 Subject: [PATCH 13/78] texture copy now works without registered_root --- pype/tools/texture_copy/app.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pype/tools/texture_copy/app.py b/pype/tools/texture_copy/app.py index a59d30ec8b..624082f00c 100644 --- a/pype/tools/texture_copy/app.py +++ b/pype/tools/texture_copy/app.py @@ -46,25 +46,25 @@ class TextureCopy: return asset def _get_destination_path(self, asset, project): - root = api.registered_root() - PROJECT = api.Session["AVALON_PROJECT"] + project_name = api.Session["AVALON_PROJECT"] hierarchy = "" parents = asset['data']['parents'] if parents and len(parents) > 0: hierarchy = os.path.join(*parents) - template_data = {"root": root, - "project": {"name": PROJECT, - "code": project['data']['code']}, - "silo": asset.get('silo'), - "asset": asset['name'], - "family": 'texture', - "subset": 'Main', - "hierarchy": hierarchy} - anatomy = Anatomy() - anatomy_filled = os.path.normpath( - anatomy.format(template_data)['texture']['path']) - return anatomy_filled + template_data = { + "project": { + "name": project_name, + "code": project['data']['code'] + }, + "silo": asset.get('silo'), + "asset": asset['name'], + "family": 'texture', + "subset": 'Main', + "hierarchy": hierarchy + } + anatomy_filled = Anatomy(project_name).format(template_data) + return os.path.normpath(anatomy_filled['texture']['path']) def _get_version(self, path): versions = [0] From 082a14937ad78e514e613a5c1d5fcbc541e28d14 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 18:41:29 +0200 Subject: [PATCH 14/78] fixed typo --- pype/ftrack/events/event_user_assigment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/events/event_user_assigment.py b/pype/ftrack/events/event_user_assigment.py index a899efbe50..bf3bec93be 100644 --- a/pype/ftrack/events/event_user_assigment.py +++ b/pype/ftrack/events/event_user_assigment.py @@ -199,7 +199,7 @@ class UserAssigmentEvent(BaseEvent): work_dir = anatomy_filled["work"]["folder"] # we also need publish but not whole anatomy_filled.strict = False - publish = anatomy_filled["publosh"]["folder"] + publish = anatomy_filled["publish"]["folder"] # now find path to {asset} m = re.search("(^.+?{})".format(data['asset']), From b7d81da40d5cea2a52e43e4bffde8250bc823974 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 18:44:46 +0200 Subject: [PATCH 15/78] fixed nuke lib workfile filling --- pype/nuke/lib.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 3f5e43332f..7419b18710 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -1095,13 +1095,14 @@ class BuildWorkfile(WorkfileSettings): # collect data for formating self.data_tmp = { "project": {"name": self._project["name"], - "code": self._project["data"].get("code", '')}, + "code": self._project["data"].get("code", "")}, "asset": self._asset or os.environ["AVALON_ASSET"], "task": kwargs.get("task") or api.Session["AVALON_TASK"], "hierarchy": kwargs.get("hierarchy") or pype.get_hierarchy(), "version": kwargs.get("version", {}).get("name", 1), "user": getpass.getuser(), - "comment": "firstBuild" + "comment": "firstBuild", + "ext": "nk" } # get presets from anatomy @@ -1111,7 +1112,7 @@ class BuildWorkfile(WorkfileSettings): # get dir and file for workfile self.work_dir = anatomy_filled["work"]["folder"] - self.work_file = anatomy_filled["work"]["path"] + ".nk" + self.work_file = anatomy_filled["work"]["file"] def save_script_as(self, path=None): # first clear anything in open window From 1983150f165aa478c555cd0f0187e3c6355602a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 18:48:27 +0200 Subject: [PATCH 16/78] added avalon project to deadline env filter --- pype/plugins/global/publish/submit_publish_job.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 6f1a85de50..f85903b186 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -152,7 +152,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_API_KEY", "FTRACK_SERVER", "PYPE_ROOT", - "PYPE_METADATA_FILE" + "PYPE_METADATA_FILE", + "AVALON_PROJECT" ] # pool used to do the publishing job From b601c20a5c17691abde707753863489e80597891 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 19:39:00 +0200 Subject: [PATCH 17/78] fixed typo 2 --- pype/plugins/global/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index f85903b186..1fc95b3e70 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -200,7 +200,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.warning(( "Could not find root path for remapping \"{}\"." " This may cause issues on farm." - ).format(output_dirt)) + ).format(output_dir)) rootless_path = output_dir else: # If root was found then use `mount` root for `output_dir` From c89ed35429f706e0c4075bb63a7532c7b2ffb9a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Mar 2020 20:54:32 +0200 Subject: [PATCH 18/78] fixed varianble --- pype/plugins/global/publish/submit_publish_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 1fc95b3e70..244c2e840d 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -588,8 +588,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # `rootless_path` is not set to `source` if none of roots match self.log.warning(( "Could not find root path for remapping \"{}\"." - " This may cause issues on farm." - ).format(output_dirt)) + " This may cause issues." + ).format(source)) families = ["render"] From 2133dfa49c7ae8dc1adac80e25ccaa4c4d46b241 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Apr 2020 15:53:31 +0200 Subject: [PATCH 19/78] fixed typos --- pype/ftrack/lib/ftrack_app_handler.py | 2 +- pype/plugins/global/publish/collect_rendered_files.py | 2 +- pype/tools/texture_copy/app.py | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index a3d3480a5e..2430f44ae7 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -264,7 +264,7 @@ class AppAction(BaseHandler): if execfile is None: return { "success": False, - "message": "We didn't found launcher for {0}".format( + "message": "We didn't find launcher for {0}".format( self.label ) } diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index e79be1c4ae..1c9a8c1f94 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -86,7 +86,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): if project_name is None: root = None self.log.warning( - "Environment `AVLAON_PROJECT` was not found." + "Environment `AVALON_PROJECT` was not found." "Could not set `root` which may cause issues." ) else: diff --git a/pype/tools/texture_copy/app.py b/pype/tools/texture_copy/app.py index 624082f00c..5f89db53ff 100644 --- a/pype/tools/texture_copy/app.py +++ b/pype/tools/texture_copy/app.py @@ -46,7 +46,7 @@ class TextureCopy: return asset def _get_destination_path(self, asset, project): - project_name = api.Session["AVALON_PROJECT"] + project_name = project["name"] hierarchy = "" parents = asset['data']['parents'] if parents and len(parents) > 0: @@ -63,8 +63,9 @@ class TextureCopy: "subset": 'Main', "hierarchy": hierarchy } - anatomy_filled = Anatomy(project_name).format(template_data) - return os.path.normpath(anatomy_filled['texture']['path']) + anatomy = Anatomy(project_name) + anatomy_filled = anatomy.format(template_data) + return anatomy_filled['texture']['path'] def _get_version(self, path): versions = [0] From 004f9280d86e0b7813c3b4d6263f03cf0becf3f2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Apr 2020 18:42:05 +0200 Subject: [PATCH 20/78] collect rendered files use root formatting --- .../global/publish/collect_rendered_files.py | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 1c9a8c1f94..97edc45bb7 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -32,7 +32,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): ) return data - def _process_path(self, data): + def _process_path(self, data, root): # validate basic necessary data data_err = "invalid json file - missing data" required = ["asset", "user", "comment", @@ -66,14 +66,32 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): os.environ["FTRACK_SERVER"] = ftrack["FTRACK_SERVER"] # now we can just add instances from json file and we are done - for instance in data.get("instances"): + for instance_data in data.get("instances"): self.log.info(" - processing instance for {}".format( - instance.get("subset"))) - i = self._context.create_instance(instance.get("subset")) - self.log.info("remapping paths ...") - i.data["representations"] = [PypeLauncher().path_remapper( - data=r) for r in instance.get("representations")] - i.data.update(instance) + instance_data.get("subset"))) + instance = self._context.create_instance( + instance_data.get("subset") + ) + self.log.info("Filling stagignDir...") + instance.data.update(instance_data) + + representations = [] + for repre_data in instance_data.get("representations") or []: + staging_dir = repre_data.get("stagingDir") + if ( + not root + or staging_dir is None + or "{root" not in staging_dir + ): + repre_data = PypeLauncher().path_remapper(data=repre_data) + + else: + repre_data["stagingDir"] = staging_dir.format( + **{"root": root} + ) + representations.append(repre_data) + + instance.data["representations"] = representations def process(self, context): self._context = context @@ -106,4 +124,4 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): os.environ.update(data.get("session")) session_set = True assert data, "failed to load json file" - self._process_path(data) + self._process_path(data, root) From 6801dc2118eea9a40bb4bd496dff2b9685128206 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 6 Apr 2020 19:01:04 +0200 Subject: [PATCH 21/78] stagingdirs are sent rootless --- .../global/publish/submit_publish_job.py | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 244c2e840d..3d991142c7 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -189,10 +189,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): output_dir = instance.data["outputDir"] # Convert output dir to `{root}/rest/of/path/...` with Anatomy anatomy = instance.context.data["anatomy"] - root_name = anatomy.templates["work"].get("root_name") + work_root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( anatomy.roots.find_root_template_from_path( - output_dir, root_name + output_dir, work_root_name ) ) if not success: @@ -426,12 +426,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): """ representations = [] - cols, rem = clique.assemble(exp_files) + collections, remainders = clique.assemble(exp_files) bake_render_path = instance.get("bakeRenderPath") # create representation for every collected sequence - for c in cols: - ext = c.tail.lstrip(".") + for collection in collections: + ext = collection.tail.lstrip(".") preview = False # if filtered aov name is found in filename, toggle it for # preview video rendering @@ -440,7 +440,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): for aov in self.aov_filter[app]: if re.match( r".+(?:\.|_)({})(?:\.|_).*".format(aov), - list(c)[0] + list(collection)[0] ): preview = True break @@ -452,11 +452,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): rep = { "name": ext, "ext": ext, - "files": [os.path.basename(f) for f in list(c)], + "files": [os.path.basename(f) for f in list(collection)], "frameStart": int(instance.get("frameStartHandle")), "frameEnd": int(instance.get("frameEndHandle")), # If expectedFile are absolute, we need only filenames - "stagingDir": os.path.dirname(list(c)[0]), + "stagingDir": os.path.dirname(list(collection)[0]), "anatomy_template": "render", "fps": instance.get("fps"), "tags": ["review", "preview"] if preview else [], @@ -467,16 +467,16 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self._solve_families(instance, preview) # add reminders as representations - for r in rem: - ext = r.split(".")[-1] + for remainder in remainders: + ext = remainder.split(".")[-1] rep = { "name": ext, "ext": ext, - "files": os.path.basename(r), - "stagingDir": os.path.dirname(r), + "files": os.path.basename(remainder), + "stagingDir": os.path.dirname(remainder), "anatomy_template": "publish", } - if r in bake_render_path: + if remainder in bake_render_path: rep.update({ "fps": instance.get("fps"), "anatomy_template": "render", @@ -571,18 +571,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): except KeyError: source = context.data["currentFile"] - anatomy = context.data["anatomy"] - root_name = anatomy.templates["work"].get("root_name") + anatomy = instance.context.data["anatomy"] + work_root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( anatomy.roots.find_root_template_from_path( - source, root_name + source, work_root_name ) ) if success: - orig_root_type = anatomy.roots._root_type - anatomy.roots._root_type = "mount" - source = rootless_path.format(**{"root": anatomy.roots}) - anatomy.roots._root_type = orig_root_type + source = rootless_path else: # `rootless_path` is not set to `source` if none of roots match @@ -627,13 +624,23 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # look into instance data if representations are not having any # which are having tag `publish_on_farm` and include them - for r in instance.data.get("representations", []): - if "publish_on_farm" in r.get("tags"): + for repre in instance.data.get("representations", []): + staging_dir = repre.get("stagingDir") + if staging_dir: + success, rootless_staging_dir = ( + anatomy.roots.find_root_template_from_path( + repre, work_root_name + ) + ) + if success: + repre["stagingDir"] = rootless_staging_dir + + if "publish_on_farm" in repre.get("tags"): # create representations attribute of not there if "representations" not in instance_skeleton_data.keys(): instance_skeleton_data["representations"] = [] - instance_skeleton_data["representations"].append(r) + instance_skeleton_data["representations"].append(repre) instances = None assert data.get("expectedFiles"), ("Submission from old Pype version" From 182502c5e4d24f9cac272e380c537cb3fa7b3740 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 12:44:24 +0200 Subject: [PATCH 22/78] replaced "PYPE_ROOT" with "PYPE_SETUP_PATH" --- docs/source/conf.py | 2 +- make_docs.bat | 8 ++++---- pype/clockify/widget_settings.py | 2 +- pype/ftrack/tray/login_dialog.py | 2 +- pype/lib.py | 4 ++-- pype/muster/widget_login.py | 2 +- pype/plugins/global/publish/submit_publish_job.py | 2 +- pype/plugins/maya/publish/submit_maya_muster.py | 2 +- pype/scripts/publish_deadline.py | 4 ++-- pype/scripts/publish_filesequence.py | 6 +++--- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 894425e56b..d022332a56 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ from pypeapp.pypeLauncher import PypeLauncher from pypeapp.storage import Storage from pypeapp.deployment import Deployment -pype_setup = os.getenv('PYPE_ROOT') +pype_setup = os.getenv('PYPE_SETUP_PATH') d = Deployment(pype_setup) launcher = PypeLauncher() diff --git a/make_docs.bat b/make_docs.bat index f0011086e5..d2ea75562f 100644 --- a/make_docs.bat +++ b/make_docs.bat @@ -25,15 +25,15 @@ set PYTHONPATH=%%d;!PYTHONPATH! echo ^>^>^> Setting PYPE_CONFIG call :ResolvePath pypeconfig "..\pype-config" set PYPE_CONFIG=%pypeconfig% -echo ^>^>^> Setting PYPE_ROOT +echo ^>^>^> Setting PYPE_SETUP_PATH call :ResolvePath pyperoot "..\..\" -set PYPE_ROOT=%pyperoot% -set PYTHONPATH=%PYPE_ROOT%;%PYTHONPATH% +set PYPE_SETUP_PATH=%pyperoot% +set PYTHONPATH=%PYPE_SETUP_PATH%;%PYTHONPATH% echo ^>^>^> Setting PYPE_ENV set PYPE_ENV="C:\Users\Public\pype_env2" call "docs\make.bat" clean -sphinx-apidoc -M -f -d 6 --ext-autodoc --ext-intersphinx --ext-viewcode -o docs\source pype %PYPE_ROOT%\repos\pype\pype\vendor\* +sphinx-apidoc -M -f -d 6 --ext-autodoc --ext-intersphinx --ext-viewcode -o docs\source pype %PYPE_SETUP_PATH%\repos\pype\pype\vendor\* call "docs\make.bat" html echo ^>^>^> Doing cleanup ... set PYTHONPATH=%_OLD_PYTHONPATH% diff --git a/pype/clockify/widget_settings.py b/pype/clockify/widget_settings.py index 7142548fa6..027268834c 100644 --- a/pype/clockify/widget_settings.py +++ b/pype/clockify/widget_settings.py @@ -26,7 +26,7 @@ class ClockifySettings(QtWidgets.QWidget): elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): self.setWindowIcon(self.parent.parent.icon) else: - pype_setup = os.getenv('PYPE_ROOT') + pype_setup = os.getenv('PYPE_SETUP_PATH') items = [pype_setup, "app", "resources", "icon.png"] fname = os.path.sep.join(items) icon = QtGui.QIcon(fname) diff --git a/pype/ftrack/tray/login_dialog.py b/pype/ftrack/tray/login_dialog.py index 5f3777f93e..88c4e90374 100644 --- a/pype/ftrack/tray/login_dialog.py +++ b/pype/ftrack/tray/login_dialog.py @@ -29,7 +29,7 @@ class Login_Dialog_ui(QtWidgets.QWidget): elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): self.setWindowIcon(self.parent.parent.icon) else: - pype_setup = os.getenv('PYPE_ROOT') + pype_setup = os.getenv('PYPE_SETUP_PATH') items = [pype_setup, "app", "resources", "icon.png"] fname = os.path.sep.join(items) icon = QtGui.QIcon(fname) diff --git a/pype/lib.py b/pype/lib.py index 824d2e0f52..ab99e6a49c 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -660,7 +660,7 @@ def execute_hook(hook, *args, **kwargs): This will load hook file, instantiate class and call `execute` method on it. Hook must be in a form: - `$PYPE_ROOT/repos/pype/path/to/hook.py/HookClass` + `$PYPE_SETUP_PATH/repos/pype/path/to/hook.py/HookClass` This will load `hook.py`, instantiate HookClass and then execute_hook `execute(*args, **kwargs)` @@ -671,7 +671,7 @@ def execute_hook(hook, *args, **kwargs): class_name = hook.split("/")[-1] - abspath = os.path.join(os.getenv('PYPE_ROOT'), + abspath = os.path.join(os.getenv('PYPE_SETUP_PATH'), 'repos', 'pype', *hook.split("/")[:-1]) mod_name, mod_ext = os.path.splitext(os.path.basename(abspath)) diff --git a/pype/muster/widget_login.py b/pype/muster/widget_login.py index 1d0dd29d59..88d769ef93 100644 --- a/pype/muster/widget_login.py +++ b/pype/muster/widget_login.py @@ -23,7 +23,7 @@ class MusterLogin(QtWidgets.QWidget): elif hasattr(parent, 'parent') and hasattr(parent.parent, 'icon'): self.setWindowIcon(parent.parent.icon) else: - pype_setup = os.getenv('PYPE_ROOT') + pype_setup = os.getenv('PYPE_SETUP_PATH') items = [pype_setup, "app", "resources", "icon.png"] fname = os.path.sep.join(items) icon = QtGui.QIcon(fname) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 3d991142c7..f8b2c80fa3 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -151,7 +151,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "PYPE_ROOT", + "PYPE_SETUP_PATH", "PYPE_METADATA_FILE", "AVALON_PROJECT" ] diff --git a/pype/plugins/maya/publish/submit_maya_muster.py b/pype/plugins/maya/publish/submit_maya_muster.py index af968ed773..fdd246d012 100644 --- a/pype/plugins/maya/publish/submit_maya_muster.py +++ b/pype/plugins/maya/publish/submit_maya_muster.py @@ -312,7 +312,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): # replace path for UNC / network share paths, co PYPE is found # over network. It assumes PYPE is located somewhere in # PYPE_CORE_PATH - pype_root = os.environ["PYPE_ROOT"].replace( + pype_root = os.environ["PYPE_SETUP_PATH"].replace( os.path.normpath(os.environ['PYPE_CORE_MOUNT']), os.path.normpath(os.environ['PYPE_CORE_PATH']) ) diff --git a/pype/scripts/publish_deadline.py b/pype/scripts/publish_deadline.py index e6052dbfd2..16d097a1ea 100644 --- a/pype/scripts/publish_deadline.py +++ b/pype/scripts/publish_deadline.py @@ -14,9 +14,9 @@ def __main__(): "configuration.") kwargs, args = parser.parse_known_args() - pype_root = os.environ.get("PYPE_ROOT") + pype_root = os.environ.get("PYPE_SETUP_PATH") if not pype_root: - raise Exception("PYPE_ROOT is not set") + raise Exception("PYPE_SETUP_PATH is not set") # TODO: set correct path pype_command = "pype.ps1" diff --git a/pype/scripts/publish_filesequence.py b/pype/scripts/publish_filesequence.py index 0a08e9ed4c..8b99d0560f 100644 --- a/pype/scripts/publish_filesequence.py +++ b/pype/scripts/publish_filesequence.py @@ -47,10 +47,10 @@ def __main__(): auto_pype_root = os.path.dirname(os.path.abspath(__file__)) auto_pype_root = os.path.abspath(auto_pype_root + "../../../../..") - auto_pype_root = os.environ.get('PYPE_ROOT') or auto_pype_root - if os.environ.get('PYPE_ROOT'): + auto_pype_root = os.environ.get('PYPE_SETUP_PATH') or auto_pype_root + if os.environ.get('PYPE_SETUP_PATH'): print("Got Pype location from environment: {}".format( - os.environ.get('PYPE_ROOT'))) + os.environ.get('PYPE_SETUP_PATH'))) pype_command = "pype.ps1" if platform.system().lower() == "linux": From 2e431b32e1f894a398a3865b4bb2ea4e8ecd06b4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 14:27:02 +0200 Subject: [PATCH 23/78] collect anatomy was split into 2 plugins to be able to collect anatomy obj before collect rendered files --- ...omy.py => collect_anatomy_context_data.py} | 45 +++++++++++-------- .../global/publish/collect_anatomy_object.py | 34 ++++++++++++++ 2 files changed, 61 insertions(+), 18 deletions(-) rename pype/plugins/global/publish/{collect_anatomy.py => collect_anatomy_context_data.py} (63%) create mode 100644 pype/plugins/global/publish/collect_anatomy_object.py diff --git a/pype/plugins/global/publish/collect_anatomy.py b/pype/plugins/global/publish/collect_anatomy_context_data.py similarity index 63% rename from pype/plugins/global/publish/collect_anatomy.py rename to pype/plugins/global/publish/collect_anatomy_context_data.py index 7fd2056213..e1e6c12ee9 100644 --- a/pype/plugins/global/publish/collect_anatomy.py +++ b/pype/plugins/global/publish/collect_anatomy_context_data.py @@ -1,13 +1,14 @@ -"""Collect Anatomy and global anatomy data. +"""Collect global context Anatomy data. Requires: + context -> anatomy + context -> projectEntity + context -> assetEntity + context -> username + context -> datetimeData session -> AVALON_TASK - projectEntity, assetEntity -> collect_avalon_entities *(pyblish.api.CollectorOrder) - username -> collect_pype_user *(pyblish.api.CollectorOrder + 0.001) - datetimeData -> collect_datetime_data *(pyblish.api.CollectorOrder) Provides: - context -> anatomy (pypeapp.Anatomy) context -> anatomyData """ @@ -15,15 +16,31 @@ import os import json from avalon import api, lib -from pypeapp import Anatomy import pyblish.api -class CollectAnatomy(pyblish.api.ContextPlugin): - """Collect Anatomy into Context""" +class CollectAnatomyContextData(pyblish.api.ContextPlugin): + """Collect Anatomy Context data. + + Example: + context.data["anatomyData"] = { + "project": { + "name": "MyProject", + "code": "myproj" + }, + "asset": "AssetName", + "hierarchy": "path/to/asset", + "task": "Working", + "username": "MeDespicable", + + *** OPTIONAL *** + "app": "maya" # Current application base name + + mutliple keys from `datetimeData` # see it's collector + } + """ order = pyblish.api.CollectorOrder + 0.002 - label = "Collect Anatomy" + label = "Collect Anatomy Context Data" def process(self, context): task_name = api.Session["AVALON_TASK"] @@ -31,13 +48,6 @@ class CollectAnatomy(pyblish.api.ContextPlugin): project_entity = context.data["projectEntity"] asset_entity = context.data["assetEntity"] - project_name = project_entity["name"] - - context.data["anatomy"] = Anatomy(project_name) - self.log.info( - "Anatomy object collected for project \"{}\".".format(project_name) - ) - hierarchy_items = asset_entity["data"]["parents"] hierarchy = "" if hierarchy_items: @@ -45,13 +55,12 @@ class CollectAnatomy(pyblish.api.ContextPlugin): context_data = { "project": { - "name": project_name, + "name": project_entity["name"], "code": project_entity["data"].get("code") }, "asset": asset_entity["name"], "hierarchy": hierarchy.replace("\\", "/"), "task": task_name, - "username": context.data["user"] } diff --git a/pype/plugins/global/publish/collect_anatomy_object.py b/pype/plugins/global/publish/collect_anatomy_object.py new file mode 100644 index 0000000000..d9e6964050 --- /dev/null +++ b/pype/plugins/global/publish/collect_anatomy_object.py @@ -0,0 +1,34 @@ +"""Collect Anatomy object. + +Requires: + os.environ -> AVALON_PROJECT + +Provides: + context -> anatomy (pypeapp.Anatomy) +""" + +from avalon import io +from pypeapp import Anatomy +import pyblish.api + + +class CollectAnatomyObject(pyblish.api.ContextPlugin): + """Collect Anatomy object into Context""" + + order = pyblish.api.CollectorOrder - 0.11 + label = "Collect Anatomy Object" + + def process(self, context): + io.install() + project_name = io.Session.get("AVALON_PROJECT") + if project_name is None: + raise AssertionError( + "Environment `AVALON_PROJECT` is not set." + "Could not initialize project's Anatomy." + ) + + context.data["anatomy"] = Anatomy(project_name) + + self.log.info( + "Anatomy object collected for project \"{}\".".format(project_name) + ) From b8efca6c3f6d4e8224ced2f5ee0277221a96fbcb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 14:27:34 +0200 Subject: [PATCH 24/78] "collect_instance_anatomy_data" renamed to "collect_anatomy_instance_data" --- ...nce_anatomy_data.py => collect_anatomy_instance_data.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename pype/plugins/global/publish/{collect_instance_anatomy_data.py => collect_anatomy_instance_data.py} (96%) diff --git a/pype/plugins/global/publish/collect_instance_anatomy_data.py b/pype/plugins/global/publish/collect_anatomy_instance_data.py similarity index 96% rename from pype/plugins/global/publish/collect_instance_anatomy_data.py rename to pype/plugins/global/publish/collect_anatomy_instance_data.py index 06a25b7c8a..6528bede2e 100644 --- a/pype/plugins/global/publish/collect_instance_anatomy_data.py +++ b/pype/plugins/global/publish/collect_anatomy_instance_data.py @@ -28,11 +28,11 @@ from avalon import io import pyblish.api -class CollectInstanceAnatomyData(pyblish.api.InstancePlugin): - """Fill templates with data needed for publish""" +class CollectAnatomyInstanceData(pyblish.api.InstancePlugin): + """Collect Instance specific Anatomy data.""" order = pyblish.api.CollectorOrder + 0.49 - label = "Collect instance anatomy data" + label = "Collect Anatomy Instance data" def process(self, instance): # get all the stuff from the database From fb9f951d924688abd8d518c3766316c9a9be5c94 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 14:28:00 +0200 Subject: [PATCH 25/78] collect rendered files use anatomy to fill metadata json path and staging dirs --- pype/__init__.py | 3 +- .../global/publish/collect_rendered_files.py | 59 ++++++++++++------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/pype/__init__.py b/pype/__init__.py index 34d2d90649..1dcd6f9fd6 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -87,8 +87,7 @@ def install(): if project_name: root_obj = Roots(project_name) - root = root_obj.roots - avalon.register_root(root) + avalon.register_root(root_obj.roots) # apply monkey patched discover to original one avalon.discover = patched_discover diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 97edc45bb7..ebb1dac127 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -1,3 +1,12 @@ +"""Loads publishing context from json and continues in publish process. + +Requires: + anatomy -> context["anatomy"] *(pyblish.api.CollectorOrder - 0.11) + +Provides: + context, instances -> All data from previous publishing process. +""" + import os import json @@ -32,7 +41,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): ) return data - def _process_path(self, data, root): + def _process_path(self, data, anatomy): # validate basic necessary data data_err = "invalid json file - missing data" required = ["asset", "user", "comment", @@ -78,17 +87,26 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): representations = [] for repre_data in instance_data.get("representations") or []: staging_dir = repre_data.get("stagingDir") - if ( - not root - or staging_dir is None - or "{root" not in staging_dir - ): - repre_data = PypeLauncher().path_remapper(data=repre_data) + if not staging_dir: + pass + + elif "{root" in staging_dir: + repre_data["stagingDir"] = staging_dir.format( + **{"root": anatomy.roots} + ) + self.log.debug(( + "stagingDir was filled with root." + " To: \"{}\" From: \"{}\"" + ).format(repre_data["stagingDir"], staging_dir)) else: - repre_data["stagingDir"] = staging_dir.format( - **{"root": root} - ) + remapped = anatomy.roots_obj.path_remapper(staging_dir) + if remapped: + repre_data["stagingDir"] = remapped + self.log.debug(( + "stagingDir was remapped. To: \"{}\" From: \"{}\"" + ).format(remapped, staging_dir)) + representations.append(repre_data) instance.data["representations"] = representations @@ -102,21 +120,20 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): project_name = os.environ.get("AVALON_PROJECT") if project_name is None: - root = None - self.log.warning( + raise AssertionError( "Environment `AVALON_PROJECT` was not found." - "Could not set `root` which may cause issues." + "Could not set project `root` which may cause issues." ) - else: - self.log.info("Getting root setting for project \"{}\"".format( - project_name - )) - root = {"root": Roots(project_name)} + # TODO root filling should happen after collect Anatomy + self.log.info("Getting root setting for project \"{}\"".format( + project_name + )) + + anatomy = context.data["anatomy"] session_set = False for path in paths: - if root: - path = path.format(**root) + path = path.format(**{"root": anatomy.roots}) data = self._load_json(path) if not session_set: self.log.info("Setting session using data from file") @@ -124,4 +141,4 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): os.environ.update(data.get("session")) session_set = True assert data, "failed to load json file" - self._process_path(data, root) + self._process_path(data, anatomy) From 0370389292e03e8f1e7f8644f7f9fc0e6b775020 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 14:46:20 +0200 Subject: [PATCH 26/78] removed unused imports --- pype/plugins/global/publish/collect_rendered_files.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index ebb1dac127..5197f71a46 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -13,8 +13,6 @@ import json import pyblish.api from avalon import api -from pypeapp import PypeLauncher, Roots - class CollectRenderedFiles(pyblish.api.ContextPlugin): """ From 56338f3d70abada98a85451f4f83ed9a3ec93b38 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 18:10:38 +0200 Subject: [PATCH 27/78] do not use PYPE_CORE_* environments --- .../global/publish/submit_publish_job.py | 8 +--- .../maya/publish/submit_maya_muster.py | 38 ++++++------------- .../nuke/publish/submit_nuke_deadline.py | 31 ++++++--------- 3 files changed, 25 insertions(+), 52 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index f8b2c80fa3..8525657b21 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -20,13 +20,7 @@ def _get_script(): if module_path.endswith(".pyc"): module_path = module_path[: -len(".pyc")] + ".py" - module_path = os.path.normpath(module_path) - mount_root = os.path.normpath(os.environ["PYPE_CORE_MOUNT"]) - network_root = os.path.normpath(os.environ["PYPE_CORE_PATH"]) - - module_path = module_path.replace(mount_root, network_root) - - return module_path + return os.path.normpath(module_path) # Logic to retrieve latest files concerning extendFrames diff --git a/pype/plugins/maya/publish/submit_maya_muster.py b/pype/plugins/maya/publish/submit_maya_muster.py index fdd246d012..c6660fe601 100644 --- a/pype/plugins/maya/publish/submit_maya_muster.py +++ b/pype/plugins/maya/publish/submit_maya_muster.py @@ -309,13 +309,7 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): output_dir = instance.data["outputDir"] metadata_path = os.path.join(output_dir, metadata_filename) - # replace path for UNC / network share paths, co PYPE is found - # over network. It assumes PYPE is located somewhere in - # PYPE_CORE_PATH - pype_root = os.environ["PYPE_SETUP_PATH"].replace( - os.path.normpath(os.environ['PYPE_CORE_MOUNT']), - os.path.normpath(os.environ['PYPE_CORE_PATH']) - ) + pype_root = os.environ["PYPE_SETUP_PATH"] # we must provide either full path to executable or use musters own # python named MPython.exe, residing directly in muster bin @@ -516,33 +510,25 @@ class MayaSubmitMuster(pyblish.api.InstancePlugin): environment["PATH"] = os.environ["PATH"] # self.log.debug("enviro: {}".format(environment['PYPE_SCRIPTS'])) clean_environment = {} - for key in environment: + for key, value in environment.items(): clean_path = "" self.log.debug("key: {}".format(key)) - to_process = environment[key] - if key == "PYPE_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') + if "://" in value: + clean_path = value else: - for path in environment[key].split(os.pathsep): + valid_paths = [] + for path in value.split(os.pathsep): + if not path: + continue try: path.decode('UTF-8', 'strict') - clean_path += os.path.normpath(path) + os.pathsep + valid_paths.append(os.path.normpath(path)) except UnicodeDecodeError: print('path contains non UTF characters') - # this should replace paths so they are pointing to network share - clean_path = clean_path.replace( - os.path.normpath(environment['PYPE_CORE_MOUNT']), - os.path.normpath(environment['PYPE_CORE_PATH'])) + if valid_paths: + clean_path = os.pathsep.join(valid_paths) + clean_environment[key] = clean_path return clean_environment diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 9ee988b5ae..81952dcd9c 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -194,36 +194,29 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): environment["PATH"] = os.environ["PATH"] # self.log.debug("enviro: {}".format(environment['PYPE_SCRIPTS'])) clean_environment = {} - for key in environment: + for key, value in environment.items(): clean_path = "" self.log.debug("key: {}".format(key)) - to_process = environment[key] - if key == "PYPE_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') + if "://" in value: + clean_path = value else: - for path in environment[key].split(os.pathsep): + valid_paths = [] + for path in value.split(os.pathsep): + if not path: + continue try: path.decode('UTF-8', 'strict') - clean_path += os.path.normpath(path) + os.pathsep + valid_paths.append(os.path.normpath(path)) except UnicodeDecodeError: print('path contains non UTF characters') + if valid_paths: + clean_path = os.pathsep.join(valid_paths) + if key == "PYTHONPATH": clean_path = clean_path.replace('python2', 'python3') - clean_path = clean_path.replace( - os.path.normpath(environment['PYPE_CORE_MOUNT']), - os.path.normpath(environment['PYPE_CORE_PATH']) - ) + self.log.debug("clean path: {}".format(clean_path)) clean_environment[key] = clean_path environment = clean_environment From 6d81ef6cd72047fce8dc408d928dabab6783cbde Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 18:27:37 +0200 Subject: [PATCH 28/78] storage is not used in pype --- docs/source/conf.py | 4 --- pype/logging/gui/widgets.py | 65 +------------------------------------ 2 files changed, 1 insertion(+), 68 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d022332a56..517c441ccd 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,11 +15,8 @@ # import os # import sys # sys.path.insert(0, os.path.abspath('.')) -import sys import os -from pprint import pprint from pypeapp.pypeLauncher import PypeLauncher -from pypeapp.storage import Storage from pypeapp.deployment import Deployment pype_setup = os.getenv('PYPE_SETUP_PATH') @@ -32,7 +29,6 @@ os.environ['PYPE_CONFIG'] = config_path os.environ['TOOL_ENV'] = os.path.normpath(os.path.join(config_path, 'environments')) launcher._add_modules() -Storage().update_environment() launcher._load_default_environments(tools=tools) # -- Project information ----------------------------------------------------- diff --git a/pype/logging/gui/widgets.py b/pype/logging/gui/widgets.py index 10aad3c282..1daaa28326 100644 --- a/pype/logging/gui/widgets.py +++ b/pype/logging/gui/widgets.py @@ -397,7 +397,7 @@ class LogDetailWidget(QtWidgets.QWidget): layout = QtWidgets.QVBoxLayout(self) label = QtWidgets.QLabel("Detail") - detail_widget = LogDetailTextEdit() + detail_widget = QtWidgets.QTextEdit() detail_widget.setReadOnly(True) layout.addWidget(label) layout.addWidget(detail_widget) @@ -420,66 +420,3 @@ class LogDetailWidget(QtWidgets.QWidget): self.detail_widget.setHtml(self.html_text.format(**data)) - - -class LogDetailTextEdit(QtWidgets.QTextEdit): - """QTextEdit that displays version specific information. - - This also overrides the context menu to add actions like copying - source path to clipboard or copying the raw data of the version - to clipboard. - - """ - def __init__(self, parent=None): - super(LogDetailTextEdit, self).__init__(parent=parent) - - # self.data = { - # "source": None, - # "raw": None - # } - # - # def contextMenuEvent(self, event): - # """Context menu with additional actions""" - # menu = self.createStandardContextMenu() - # - # # Add additional actions when any text so we can assume - # # the version is set. - # if self.toPlainText().strip(): - # - # menu.addSeparator() - # action = QtWidgets.QAction("Copy source path to clipboard", - # menu) - # action.triggered.connect(self.on_copy_source) - # menu.addAction(action) - # - # action = QtWidgets.QAction("Copy raw data to clipboard", - # menu) - # action.triggered.connect(self.on_copy_raw) - # menu.addAction(action) - # - # menu.exec_(event.globalPos()) - # del menu - # - # def on_copy_source(self): - # """Copy formatted source path to clipboard""" - # source = self.data.get("source", None) - # if not source: - # return - # - # # path = source.format(root=api.registered_root()) - # # clipboard = QtWidgets.QApplication.clipboard() - # # clipboard.setText(path) - # - # def on_copy_raw(self): - # """Copy raw version data to clipboard - # - # The data is string formatted with `pprint.pformat`. - # - # """ - # raw = self.data.get("raw", None) - # if not raw: - # return - # - # raw_text = pprint.pformat(raw) - # clipboard = QtWidgets.QApplication.clipboard() - # clipboard.setText(raw_text) From 5b92826e1f5f9453b0e42ecb4350fea65703dafb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 9 Apr 2020 19:16:05 +0200 Subject: [PATCH 29/78] launched applications have set root environments --- pype/ftrack/lib/ftrack_app_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 2430f44ae7..407a0764a4 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -215,6 +215,7 @@ class AppAction(BaseHandler): "AVALON_HIERARCHY": hierarchy, "AVALON_WORKDIR": workdir }) + prep_env.update(anatomy.roots_obj.root_environments()) # collect all parents from the task parents = [] From 7b4163271be8e8535336a166ac53c9fd5f051923 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Apr 2020 18:42:10 +0200 Subject: [PATCH 30/78] prepare project created roots --- pype/ftrack/actions/action_prepare_project.py | 363 +++++++++++------- 1 file changed, 226 insertions(+), 137 deletions(-) diff --git a/pype/ftrack/actions/action_prepare_project.py b/pype/ftrack/actions/action_prepare_project.py index 4cc6cfd8df..9b21febb81 100644 --- a/pype/ftrack/actions/action_prepare_project.py +++ b/pype/ftrack/actions/action_prepare_project.py @@ -1,9 +1,8 @@ import os import json -from ruamel import yaml from pype.ftrack import BaseAction -from pypeapp import config +from pypeapp import config, Anatomy, project_overrides_dir_path from pype.ftrack.lib.avalon_sync import get_avalon_attr @@ -24,6 +23,7 @@ class PrepareProject(BaseAction): # Key to store info about trigerring create folder structure create_project_structure_key = "create_folder_structure" + item_splitter = {'type': 'label', 'value': '---'} def discover(self, session, entities, event): ''' Validation ''' @@ -41,15 +41,190 @@ class PrepareProject(BaseAction): # Inform user that this may take a while self.show_message(event, "Preparing data... Please wait", True) + self.log.debug("Preparing data which will be shown") self.log.debug("Loading custom attributes") - cust_attrs, hier_cust_attrs = get_avalon_attr(session, True) - project_defaults = config.get_presets( - entities[0]["full_name"] - ).get("ftrack", {}).get("project_defaults", {}) - self.log.debug("Preparing data which will be shown") + project_name = entities[0]["full_name"] + + project_defaults = ( + config.get_presets(project_name) + .get("ftrack", {}) + .get("project_defaults", {}) + ) + + anatomy = Anatomy(project_name) + if not anatomy.roots: + return { + "success": False, + "message": ( + "Have issues with loading Roots for project \"{}\"." + ).format(anatomy.project_name) + } + + root_items = self.prepare_root_items(anatomy) + + ca_items, multiselect_enumerators = ( + self.prepare_custom_attribute_items(project_defaults) + ) + + self.log.debug("Heavy items are ready. Preparing last items group.") + + title = "Prepare Project" + items = [] + + # Add root items + items.extend(root_items) + items.append(self.item_splitter) + + # Ask if want to trigger Action Create Folder Structure + items.append({ + "type": "label", + "value": "

Want to create basic Folder Structure?

" + }) + items.append({ + "name": self.create_project_structure_key, + "type": "boolean", + "value": False, + "label": "Check if Yes" + }) + + items.append(self.item_splitter) + items.append({ + "type": "label", + "value": "

Set basic Attributes:

" + }) + + items.extend(ca_items) + + # This item will be last (before enumerators) + # - sets value of auto synchronization + auto_sync_name = "avalon_auto_sync" + auto_sync_item = { + "name": auto_sync_name, + "type": "boolean", + "value": project_defaults.get(auto_sync_name, False), + "label": "AutoSync to Avalon" + } + # Add autosync attribute + items.append(auto_sync_item) + + # Add enumerator items at the end + for item in multiselect_enumerators: + items.append(item) + + return { + "items": items, + "title": title + } + + def prepare_root_items(self, anatomy): + root_items = [] + self.log.debug("Root items preparation begins.") + + root_names = anatomy.root_names() + roots = anatomy.roots + + root_items.append({ + "type": "label", + "value": "

Check your Project root settings

" + }) + root_items.append({ + "type": "label", + "value": ( + "

NOTE: Roots are crutial for path filling" + " (and creating folder structure).

" + ) + }) + root_items.append({ + "type": "label", + "value": ( + "

WARNING: Do not change roots on running project," + " that will cause workflow issues.

" + ) + }) + + default_roots = anatomy.roots + while isinstance(default_roots, dict): + key = tuple(default_roots.keys())[0] + default_roots = default_roots[key] + + empty_text = "Enter root path here..." + + # Root names is None when anatomy templates contain "{root}" + all_platforms = ["windows", "linux", "darwin"] + if root_names is None: + root_items.append(self.item_splitter) + # find first possible key + for platform in all_platforms: + value = default_roots.raw_data.get(platform) or "" + root_items.append({ + "label": platform, + "name": "__root__{}".format(platform), + "type": "text", + "value": value, + "empty_text": empty_text + }) + return root_items + + root_name_data = {} + missing_roots = [] + for root_name in root_names: + root_name_data[root_name] = {} + if not isinstance(roots, dict): + missing_roots.append(root_name) + continue + + root_item = roots.get(root_name) + if not root_item: + missing_roots.append(root_name) + continue + + for platform in all_platforms: + root_name_data[root_name][platform] = ( + root_item.raw_data.get(platform) or "" + ) + + if missing_roots: + default_values = {} + for platform in all_platforms: + default_values[platform] = ( + default_roots.raw_data.get(platform) or "" + ) + + for root_name in missing_roots: + root_name_data[root_name] = default_values + + root_names = list(root_name_data.keys()) + root_items.append({ + "type": "hidden", + "name": "__rootnames__", + "value": json.dumps(root_names) + }) + + for root_name, values in root_name_data.items(): + root_items.append(self.item_splitter) + root_items.append({ + "type": "label", + "value": "Root: \"{}\"".format(root_name) + }) + for platform, value in values.items(): + root_items.append({ + "label": platform, + "name": "__root__{}{}".format(root_name, platform), + "type": "text", + "value": value, + "empty_text": empty_text + }) + + self.log.debug("Root items preparation ended.") + return root_items + + def _attributes_to_set(self, project_defaults): attributes_to_set = {} + + cust_attrs, hier_cust_attrs = get_avalon_attr(self.session, True) + for attr in hier_cust_attrs: key = attr["key"] if key.startswith("avalon_"): @@ -77,45 +252,17 @@ class PrepareProject(BaseAction): attributes_to_set.items(), key=lambda x: x[1]["label"] )) + return attributes_to_set + + def prepare_custom_attribute_items(self, project_defaults): + items = [] + multiselect_enumerators = [] + attributes_to_set = self._attributes_to_set(project_defaults) + self.log.debug("Preparing interface for keys: \"{}\"".format( str([key for key in attributes_to_set]) )) - item_splitter = {'type': 'label', 'value': '---'} - title = "Prepare Project" - items = [] - - # Ask if want to trigger Action Create Folder Structure - items.append({ - "type": "label", - "value": "

Want to create basic Folder Structure?

" - }) - - items.append({ - "name": self.create_project_structure_key, - "type": "boolean", - "value": False, - "label": "Check if Yes" - }) - - items.append(item_splitter) - items.append({ - "type": "label", - "value": "

Set basic Attributes:

" - }) - - multiselect_enumerators = [] - - # This item will be last (before enumerators) - # - sets value of auto synchronization - auto_sync_name = "avalon_auto_sync" - auto_sync_item = { - "name": auto_sync_name, - "type": "boolean", - "value": project_defaults.get(auto_sync_name, False), - "label": "AutoSync to Avalon" - } - for key, in_data in attributes_to_set.items(): attr = in_data["object"] @@ -139,8 +286,7 @@ class PrepareProject(BaseAction): attr_config_data = json.loads(attr_config["data"]) if attr_config["multiSelect"] is True: - multiselect_enumerators.append(item_splitter) - + multiselect_enumerators.append(self.item_splitter) multiselect_enumerators.append({ "type": "label", "value": in_data["label"] @@ -160,10 +306,7 @@ class PrepareProject(BaseAction): "label": "- {}".format(option["menu"]) } if default: - if ( - isinstance(default, list) or - isinstance(default, tuple) - ): + if isinstance(default, (list, tuple)): if name in default: item["value"] = True else: @@ -204,17 +347,7 @@ class PrepareProject(BaseAction): items.append(item) - # Add autosync attribute - items.append(auto_sync_item) - - # Add enumerator items at the end - for item in multiselect_enumerators: - items.append(item) - - return { - 'items': items, - 'title': title - } + return items, multiselect_enumerators def launch(self, session, entities, event): if not event['data'].get('values', {}): @@ -222,6 +355,35 @@ class PrepareProject(BaseAction): in_data = event['data']['values'] + root_values = {} + root_key = "__root__" + for key, value in tuple(in_data.items()): + if key.startswith(root_key): + _key = key[len(root_key):] + root_values[_key] = in_data.pop(key) + + root_names = in_data.pop("__rootnames__", None) + root_data = {} + if root_names: + for root_name in json.loads(root_names): + root_data[root_name] = {} + for key, value in tuple(root_values.items()): + if key.startswith(root_name): + _key = key[len(root_name):] + root_data[root_name][_key] = value + + else: + for key, value in root_values.items(): + root_data[key] = value + + project_name = entities[0]["full_name"] + anatomy = Anatomy(project_name) + anatomy.templates_obj.save_project_overrides(project_name) + anatomy.roots_obj.save_project_overrides( + project_name, root_data, override=True + ) + anatomy.reset() + # pop out info about creating project structure create_proj_struct = in_data.pop(self.create_project_structure_key) @@ -269,94 +431,22 @@ class PrepareProject(BaseAction): def create_project_specific_config(self, project_name, json_data): self.log.debug("*** Creating project specifig configs ***") - - path_proj_configs = os.environ.get('PYPE_PROJECT_CONFIGS', "") - - # Skip if PYPE_PROJECT_CONFIGS is not set - # TODO show user OS message - if not path_proj_configs: - self.log.warning(( - "Environment variable \"PYPE_PROJECT_CONFIGS\" is not set." - " Project specific config can't be set." - )) - return - - path_proj_configs = os.path.normpath(path_proj_configs) - # Skip if path does not exist - # TODO create if not exist?!!! - if not os.path.exists(path_proj_configs): - self.log.warning(( - "Path set in Environment variable \"PYPE_PROJECT_CONFIGS\"" - " Does not exist." - )) - return - - project_specific_path = os.path.normpath( - os.path.join(path_proj_configs, project_name) - ) + project_specific_path = project_overrides_dir_path(project_name) if not os.path.exists(project_specific_path): os.makedirs(project_specific_path) self.log.debug(( "Project specific config folder for project \"{}\" created." ).format(project_name)) - # Anatomy #################################### - self.log.debug("--- Processing Anatomy Begins: ---") - - anatomy_dir = os.path.normpath(os.path.join( - project_specific_path, "anatomy" - )) - anatomy_path = os.path.normpath(os.path.join( - anatomy_dir, "default.yaml" - )) - - anatomy = None - if os.path.exists(anatomy_path): - self.log.debug( - "Anatomy file already exist. Trying to read: \"{}\"".format( - anatomy_path - ) - ) - # Try to load data - with open(anatomy_path, 'r') as file_stream: - try: - anatomy = yaml.load(file_stream, Loader=yaml.loader.Loader) - self.log.debug("Reading Anatomy file was successful") - except yaml.YAMLError as exc: - self.log.warning( - "Reading Yaml file failed: \"{}\"".format(anatomy_path), - exc_info=True - ) - - if not anatomy: - self.log.debug("Anatomy is not set. Duplicating default.") - # Create Anatomy folder - if not os.path.exists(anatomy_dir): - self.log.debug( - "Creating Anatomy folder: \"{}\"".format(anatomy_dir) - ) - os.makedirs(anatomy_dir) - - source_items = [ - os.environ["PYPE_CONFIG"], "anatomy", "default.yaml" - ] - - source_path = os.path.normpath(os.path.join(*source_items)) - with open(source_path, 'r') as file_stream: - source_data = file_stream.read() - - with open(anatomy_path, 'w') as file_stream: - file_stream.write(source_data) - # Presets #################################### self.log.debug("--- Processing Presets Begins: ---") - project_defaults_dir = os.path.normpath(os.path.join(*[ + project_defaults_dir = os.path.normpath(os.path.join( project_specific_path, "presets", "ftrack" - ])) - project_defaults_path = os.path.normpath(os.path.join(*[ + )) + project_defaults_path = os.path.normpath(os.path.join( project_defaults_dir, "project_defaults.json" - ])) + )) # Create folder if not exist if not os.path.exists(project_defaults_dir): self.log.debug("Creating Ftrack Presets folder: \"{}\"".format( @@ -372,5 +462,4 @@ class PrepareProject(BaseAction): def register(session, plugins_presets={}): '''Register plugin. Called when used as an plugin.''' - PrepareProject(session, plugins_presets).register() From 37135599150878be9091b85980570440ac4bd338 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Apr 2020 18:52:00 +0200 Subject: [PATCH 31/78] small upgrade --- pype/ftrack/actions/action_prepare_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_prepare_project.py b/pype/ftrack/actions/action_prepare_project.py index 9b21febb81..2693a5750b 100644 --- a/pype/ftrack/actions/action_prepare_project.py +++ b/pype/ftrack/actions/action_prepare_project.py @@ -357,7 +357,7 @@ class PrepareProject(BaseAction): root_values = {} root_key = "__root__" - for key, value in tuple(in_data.items()): + for key in tuple(in_data.keys()): if key.startswith(root_key): _key = key[len(root_key):] root_values[_key] = in_data.pop(key) From 4a3b98c94516c9a66480f1e6ee0988dfd33a942d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 11:08:47 +0200 Subject: [PATCH 32/78] action_create_fodler uses new anatomy --- pype/ftrack/actions/action_create_folders.py | 298 +++++++------------ 1 file changed, 109 insertions(+), 189 deletions(-) diff --git a/pype/ftrack/actions/action_create_folders.py b/pype/ftrack/actions/action_create_folders.py index 80618e67e8..8f3358cf9c 100644 --- a/pype/ftrack/actions/action_create_folders.py +++ b/pype/ftrack/actions/action_create_folders.py @@ -1,30 +1,16 @@ import os -import sys -import logging -import argparse -import re - -import ftrack_api from pype.ftrack import BaseAction from avalon import lib as avalonlib -from pype.ftrack.lib.io_nonsingleton import DbConnector from pypeapp import config, Anatomy class CreateFolders(BaseAction): - #: Action identifier. - identifier = 'create.folders' - - #: Action label. - label = 'Create Folders' - - #: Action Icon. - icon = '{}/ftrack/action_icons/CreateFolders.svg'.format( - os.environ.get('PYPE_STATICS_SERVER', '') + identifier = "create.folders" + label = "Create Folders" + icon = "{}/ftrack/action_icons/CreateFolders.svg".format( + os.environ.get("PYPE_STATICS_SERVER", "") ) - db = DbConnector() - def discover(self, session, entities, event): if len(entities) != 1: return False @@ -90,49 +76,52 @@ class CreateFolders(BaseAction): with_childrens = event["data"]["values"]["children_included"] entity = entities[0] - if entity.entity_type.lower() == 'project': + if entity.entity_type.lower() == "project": proj = entity else: - proj = entity['project'] - project_name = proj['full_name'] - project_code = proj['name'] + proj = entity["project"] + project_name = proj["full_name"] + project_code = proj["name"] - if entity.entity_type.lower() == 'project' and with_childrens == False: + if entity.entity_type.lower() == 'project' and with_childrens is False: return { 'success': True, 'message': 'Nothing was created' } - data = { - "root": os.environ["AVALON_PROJECTS"], - "project": { - "name": project_name, - "code": project_code - } - } + all_entities = [] all_entities.append(entity) if with_childrens: all_entities = self.get_notask_children(entity) - av_project = None - try: - self.db.install() - self.db.Session['AVALON_PROJECT'] = project_name - av_project = self.db.find_one({'type': 'project'}) - template_work = av_project['config']['template']['work'] - template_publish = av_project['config']['template']['publish'] - self.db.uninstall() - except Exception: - templates = Anatomy().templates - template_work = templates["avalon"]["work"] - template_publish = templates["avalon"]["publish"] + anatomy = Anatomy(project_name) + + work_keys = ["work", "folder"] + work_template = anatomy.templates + for key in work_keys: + work_template = work_template[key] + work_has_apps = "{app" in work_template + + publish_keys = ["publish", "folder"] + publish_template = anatomy.templates + for key in publish_keys: + publish_template = publish_template[key] + publish_has_apps = "{app" in publish_template + + presets = config.get_presets() + app_presets = presets.get("tools", {}).get("sw_folders") + cached_apps = {} collected_paths = [] - presets = config.get_presets()["tools"]["sw_folders"] for entity in all_entities: if entity.entity_type.lower() == "project": continue - ent_data = data.copy() + ent_data = { + "project": { + "name": project_name, + "code": project_code + } + } ent_data["asset"] = entity["name"] @@ -144,69 +133,72 @@ class CreateFolders(BaseAction): ent_data["hierarchy"] = hierarchy tasks_created = False - if entity['children']: - for child in entity['children']: - if child['object_type']['name'].lower() != 'task': - continue - tasks_created = True - task_type_name = child['type']['name'].lower() - task_data = ent_data.copy() - task_data['task'] = child['name'] - possible_apps = presets.get(task_type_name, []) - template_work_created = False - template_publish_created = False - apps = [] + for child in entity["children"]: + if child["object_type"]["name"].lower() != "task": + continue + tasks_created = True + task_type_name = child["type"]["name"].lower() + task_data = ent_data.copy() + task_data["task"] = child["name"] + + apps = [] + if app_presets and (work_has_apps or publish_has_apps): + possible_apps = app_presets.get(task_type_name, []) for app in possible_apps: - try: - app_data = avalonlib.get_application(app) - app_dir = app_data['application_dir'] - except ValueError: - app_dir = app + if app in cached_apps: + app_dir = cached_apps[app] + else: + try: + app_data = avalonlib.get_application(app) + app_dir = app_data["application_dir"] + except ValueError: + app_dir = app + cached_apps[app] = app_dir apps.append(app_dir) - # Template wok - if '{app}' in template_work: - for app in apps: - template_work_created = True - app_data = task_data.copy() - app_data['app'] = app - collected_paths.append( - self.compute_template( - template_work, app_data - ) - ) - if template_work_created is False: - collected_paths.append( - self.compute_template(template_work, task_data) - ) - # Template publish - if '{app}' in template_publish: - for app in apps: - template_publish_created = True - app_data = task_data.copy() - app_data['app'] = app - collected_paths.append( - self.compute_template( - template_publish, app_data, True - ) - ) - if template_publish_created is False: - collected_paths.append( - self.compute_template( - template_publish, task_data, True - ) - ) + # Template wok + if work_has_apps: + app_data = task_data.copy() + for app in apps: + app_data["app"] = app + collected_paths.append(self.compute_template( + anatomy, app_data, work_keys + )) + else: + collected_paths.append(self.compute_template( + anatomy, task_data, work_keys + )) + + # Template publish + if publish_has_apps: + app_data = task_data.copy() + for app in apps: + app_data["app"] = app + collected_paths.append(self.compute_template( + anatomy, app_data, publish_keys + )) + else: + collected_paths.append(self.compute_template( + anatomy, task_data, publish_keys + )) if not tasks_created: # create path for entity - collected_paths.append( - self.compute_template(template_work, ent_data) - ) - collected_paths.append( - self.compute_template(template_publish, ent_data) - ) - if len(collected_paths) > 0: - self.log.info('Creating folders:') + collected_paths.append(self.compute_template( + anatomy, ent_data, work_keys + )) + collected_paths.append(self.compute_template( + anatomy, ent_data, publish_keys + )) + + if len(collected_paths) == 0: + return { + "success": True, + "message": "No project folders to create." + } + + self.log.info("Creating folders:") + for path in set(collected_paths): self.log.info(path) if not os.path.exists(path): @@ -219,100 +211,28 @@ class CreateFolders(BaseAction): def get_notask_children(self, entity): output = [] - if entity.get('object_type', {}).get( - 'name', entity.entity_type - ).lower() == 'task': + if entity.entity_type.lower() == "task": return output - else: - output.append(entity) - if entity['children']: - for child in entity['children']: - output.extend(self.get_notask_children(child)) + + output.append(entity) + for child in entity["children"]: + output.extend(self.get_notask_children(child)) return output - def template_format(self, template, data): + def compute_template(self, anatomy, data, anatomy_keys): + filled_template = anatomy.format_all(data) + for key in anatomy_keys: + filled_template = filled_template[key] - partial_data = PartialDict(data) + if filled_template.solved: + return os.path.normpath(filled_template) - # remove subdict items from string (like 'project[name]') - subdict = PartialDict() - count = 1 - store_pattern = 5*'_'+'{:0>3}' - regex_patern = "\{\w*\[[^\}]*\]\}" - matches = re.findall(regex_patern, template) - - for match in matches: - key = store_pattern.format(count) - subdict[key] = match - template = template.replace(match, '{'+key+'}') - count += 1 - # solve fillind keys with optional keys - solved = self._solve_with_optional(template, partial_data) - # try to solve subdict and replace them back to string - for k, v in subdict.items(): - try: - v = v.format_map(data) - except (KeyError, TypeError): - pass - subdict[k] = v - - return solved.format_map(subdict) - - def _solve_with_optional(self, template, data): - # Remove optional missing keys - pattern = re.compile(r"(<.*?[^{0]*>)[^0-9]*?") - invalid_optionals = [] - for group in pattern.findall(template): - try: - group.format(**data) - except KeyError: - invalid_optionals.append(group) - for group in invalid_optionals: - template = template.replace(group, "") - - solved = template.format_map(data) - - # solving after format optional in second round - for catch in re.compile(r"(<.*?[^{0]*>)[^0-9]*?").findall(solved): - if "{" in catch: - # remove all optional - solved = solved.replace(catch, "") - else: - # Remove optional symbols - solved = solved.replace(catch, catch[1:-1]) - - return solved - - def compute_template(self, str, data, task=False): - first_result = self.template_format(str, data) - if first_result == first_result.split('{')[0]: - return os.path.normpath(first_result) - if task: - return os.path.normpath(first_result.split('{')[0]) - - index = first_result.index('{') - - regex = '\{\w*[^\}]*\}' - match = re.findall(regex, first_result[index:])[0] - without_missing = str.split(match)[0].split('}') - output_items = [] - for part in without_missing: - if '{' in part: - output_items.append(part + '}') - return os.path.normpath( - self.template_format(''.join(output_items), data) + self.log.warning( + "Template \"{}\" was not fully filled \"{}\"".format( + filled_template.template, filled_template + ) ) - - -class PartialDict(dict): - def __getitem__(self, item): - out = super().__getitem__(item) - if isinstance(out, dict): - return '{'+item+'}' - return out - - def __missing__(self, key): - return '{'+key+'}' + return os.path.normpath(filled_template.split("{")[0]) def register(session, plugins_presets={}): From 0b2c1fc99f3c10e90d7b489786991a27f41af959 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 11:19:12 +0200 Subject: [PATCH 33/78] added get_project_from_entity to ftrack base event handler --- pype/ftrack/lib/ftrack_base_handler.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pype/ftrack/lib/ftrack_base_handler.py b/pype/ftrack/lib/ftrack_base_handler.py index f11cb020e9..952ac2e74f 100644 --- a/pype/ftrack/lib/ftrack_base_handler.py +++ b/pype/ftrack/lib/ftrack_base_handler.py @@ -623,3 +623,19 @@ class BaseHandler(object): self.log.debug(( "Publishing event: {}" ).format(str(event.__dict__))) + + def get_project_from_entity(self, entity): + low_entity_type = entity.entity_type.lower() + if low_entity_type == "project": + return entity + + if low_entity_type == "reviewsession": + return entity["project"] + + if low_entity_type == "filecomponent": + entity = entity["version"] + + project_data = entity["link"][0] + return self.session.query( + "Project where id is {}".format(project_data["id"]) + ).one() From 1cb3f2c8474193aa692b1c23d3e415f0ae8515f8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 12:10:12 +0200 Subject: [PATCH 34/78] formatting preparation action create project structure --- .../action_create_project_structure.py | 127 +++++------------- 1 file changed, 35 insertions(+), 92 deletions(-) diff --git a/pype/ftrack/actions/action_create_project_structure.py b/pype/ftrack/actions/action_create_project_structure.py index 6124ebe843..163ac4836e 100644 --- a/pype/ftrack/actions/action_create_project_structure.py +++ b/pype/ftrack/actions/action_create_project_structure.py @@ -1,36 +1,26 @@ import os -import sys import re -import argparse -import logging -import ftrack_api from pype.ftrack import BaseAction -from pypeapp import config +from pypeapp import config, Anatomy class CreateProjectFolders(BaseAction): - '''Edit meta data action.''' - #: Action identifier. - identifier = 'create.project.structure' - #: Action label. - label = 'Create Project Structure' - #: Action description. - description = 'Creates folder structure' - #: roles that are allowed to register this action - role_list = ['Pypeclub', 'Administrator', 'Project Manager'] - icon = '{}/ftrack/action_icons/CreateProjectFolders.svg'.format( - os.environ.get('PYPE_STATICS_SERVER', '') + identifier = "create.project.structure" + label = "Create Project Structure" + description = "Creates folder structure" + role_list = ["Pypeclub", "Administrator", "Project Manager"] + icon = "{}/ftrack/action_icons/CreateProjectFolders.svg".format( + os.environ.get("PYPE_STATICS_SERVER", "") ) - pattern_array = re.compile('\[.*\]') - pattern_ftrack = '.*\[[.]*ftrack[.]*' - pattern_ent_ftrack = 'ftrack\.[^.,\],\s,]*' - project_root_key = '__project_root__' + pattern_array = re.compile(r"\[.*\]") + pattern_ftrack = re.compile(r".*\[[.]*ftrack[.]*") + pattern_ent_ftrack = re.compile(r"ftrack\.[^.,\],\s,]*") + project_root_key = "__project_root__" def discover(self, session, entities, event): - ''' Validation ''' if len(entities) != 1: return False @@ -41,22 +31,19 @@ class CreateProjectFolders(BaseAction): def launch(self, session, entities, event): entity = entities[0] - if entity.entity_type.lower() == 'project': - project = entity - else: - project = entity['project'] - - presets = config.get_presets()['tools']['project_folder_structure'] + project = self.get_project_from_entity(entity) + presets = config.get_presets()["tools"]["project_folder_structure"] try: # Get paths based on presets basic_paths = self.get_path_items(presets) self.create_folders(basic_paths, entity) self.create_ftrack_entities(basic_paths, project) - except Exception as e: + + except Exception as exc: session.rollback() return { - 'success': False, - 'message': str(e) + "success": False, + "message": str(exc) } return True @@ -113,15 +100,15 @@ class CreateProjectFolders(BaseAction): def trigger_creation(self, separation, parent): for item, subvalues in separation.items(): matches = re.findall(self.pattern_array, item) - ent_type = 'Folder' + ent_type = "Folder" if len(matches) == 0: name = item else: match = matches[0] - name = item.replace(match, '') + name = item.replace(match, "") ent_type_match = re.findall(self.pattern_ent_ftrack, match) if len(ent_type_match) > 0: - ent_type_split = ent_type_match[0].split('.') + ent_type_split = ent_type_match[0].split(".") if len(ent_type_split) == 2: ent_type = ent_type_split[1] new_parent = self.create_ftrack_entity(name, ent_type, parent) @@ -130,22 +117,22 @@ class CreateProjectFolders(BaseAction): self.trigger_creation(subvalue, new_parent) def create_ftrack_entity(self, name, ent_type, parent): - for children in parent['children']: - if children['name'] == name: + for children in parent["children"]: + if children["name"] == name: return children data = { - 'name': name, - 'parent_id': parent['id'] + "name": name, + "parent_id": parent["id"] } - if parent.entity_type.lower() == 'project': - data['project_id'] = parent['id'] + if parent.entity_type.lower() == "project": + data["project_id"] = parent["id"] else: - data['project_id'] = parent['project']['id'] + data["project_id"] = parent["project"]["id"] existing_entity = self.session.query(( "TypedContext where name is \"{}\" and " "parent_id is \"{}\" and project_id is \"{}\"" - ).format(name, data['parent_id'], data['project_id'])).first() + ).format(name, data["parent_id"], data["project_id"])).first() if existing_entity: return existing_entity @@ -161,12 +148,11 @@ class CreateProjectFolders(BaseAction): else: paths = self.get_path_items(value) for path in paths: - if isinstance(path, str): - output.append([key, path]) - else: - p = [key] - p.extend(path) - output.append(p) + if not isinstance(path, (list, tuple)): + path = [path] + + output.append([key, *path]) + return output def compute_paths(self, basic_paths_items, project_root): @@ -176,7 +162,7 @@ class CreateProjectFolders(BaseAction): for path_item in path_items: matches = re.findall(self.pattern_array, path_item) if len(matches) > 0: - path_item = path_item.replace(matches[0], '') + path_item = path_item.replace(matches[0], "") if path_item == self.project_root_key: path_item = project_root clean_items.append(path_item) @@ -193,55 +179,12 @@ class CreateProjectFolders(BaseAction): project_root = os.path.sep.join(project_root_items) full_paths = self.compute_paths(basic_paths, project_root) - #Create folders + # Create folders for path in full_paths: if os.path.exists(path): continue os.makedirs(path.format(project_root=project_root)) - - def register(session, plugins_presets={}): - '''Register plugin. Called when used as an plugin.''' - CreateProjectFolders(session, plugins_presets).register() - - -def main(arguments=None): - '''Set up logging and register action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - # Set up basic logging - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - session = ftrack_api.Session() - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) From 65098129dd592c69784451c0c95204299a46875e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 12:11:56 +0200 Subject: [PATCH 35/78] delivery action should work with new anatomy --- pype/ftrack/actions/action_delivery.py | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/pype/ftrack/actions/action_delivery.py b/pype/ftrack/actions/action_delivery.py index 29fdfe39ae..9d686929de 100644 --- a/pype/ftrack/actions/action_delivery.py +++ b/pype/ftrack/actions/action_delivery.py @@ -2,7 +2,6 @@ import os import copy import shutil import collections -import string import clique from bson.objectid import ObjectId @@ -17,24 +16,18 @@ from pype.ftrack.lib.avalon_sync import CustAttrIdKey class Delivery(BaseAction): - '''Edit meta data action.''' - #: Action identifier. identifier = "delivery.action" - #: Action label. label = "Delivery" - #: Action description. description = "Deliver data to client" - #: roles that are allowed to register this action role_list = ["Pypeclub", "Administrator", "Project manager"] - icon = '{}/ftrack/action_icons/Delivery.svg'.format( - os.environ.get('PYPE_STATICS_SERVER', '') + icon = "{}/ftrack/action_icons/Delivery.svg".format( + os.environ.get("PYPE_STATICS_SERVER", "") ) db_con = DbConnector() def discover(self, session, entities, event): - ''' Validation ''' for entity in entities: if entity.entity_type.lower() == "assetversion": return True @@ -301,17 +294,10 @@ class Delivery(BaseAction): repre = repres_by_name.get(comp_name) repres_to_deliver.append(repre) - if not location_path: - location_path = os.environ.get("AVALON_PROJECTS") or "" - - print(location_path) - anatomy = Anatomy(project_name) for repre in repres_to_deliver: # Get destination repre path anatomy_data = copy.deepcopy(repre["context"]) - anatomy_data["root"] = location_path - anatomy_filled = anatomy.format_all(anatomy_data) test_path = anatomy_filled["delivery"][anatomy_name] @@ -341,7 +327,7 @@ class Delivery(BaseAction): self.report_items[msg].append(sub_msg) self.log.warning( "{} Representation: \"{}\" Filled: <{}>".format( - msg, str(repre["_id"]), str(result) + msg, str(repre["_id"]), str(test_path) ) ) continue @@ -352,7 +338,7 @@ class Delivery(BaseAction): if frame: repre["context"]["frame"] = len(str(frame)) * "#" - repre_path = self.path_from_represenation(repre) + repre_path = self.path_from_represenation(repre, anatomy) # TODO add backup solution where root of path from component # is repalced with AVALON_PROJECTS root if not frame: @@ -452,7 +438,7 @@ class Delivery(BaseAction): self.copy_file(src, dst) - def path_from_represenation(self, representation): + def path_from_represenation(self, representation, anatomy): try: template = representation["data"]["template"] @@ -461,7 +447,7 @@ class Delivery(BaseAction): try: context = representation["context"] - context["root"] = os.environ.get("AVALON_PROJECTS") or "" + context["root"] = anatomy.roots path = pipeline.format_template_with_optional_keys( context, template ) From 7a91ec8dce18c26bde3701539393862ae5253faf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 12:16:42 +0200 Subject: [PATCH 36/78] action delete old versions is ready to use new anatomy roots --- .../ftrack/actions/action_delete_old_versions.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pype/ftrack/actions/action_delete_old_versions.py b/pype/ftrack/actions/action_delete_old_versions.py index f6a66318c9..c13845f58c 100644 --- a/pype/ftrack/actions/action_delete_old_versions.py +++ b/pype/ftrack/actions/action_delete_old_versions.py @@ -7,6 +7,7 @@ from pymongo import UpdateOne from pype.ftrack import BaseAction from pype.ftrack.lib.io_nonsingleton import DbConnector +from pypeapp import Anatomy import avalon.pipeline @@ -21,8 +22,8 @@ class DeleteOldVersions(BaseAction): " archived with only lates versions." ) role_list = ["Pypeclub", "Project Manager", "Administrator"] - icon = '{}/ftrack/action_icons/PypeAdmin.svg'.format( - os.environ.get('PYPE_STATICS_SERVER', '') + icon = "{}/ftrack/action_icons/PypeAdmin.svg".format( + os.environ.get("PYPE_STATICS_SERVER", "") ) dbcon = DbConnector() @@ -194,6 +195,7 @@ class DeleteOldVersions(BaseAction): # Set Mongo collection project_name = project["full_name"] + anatomy = Anatomy(project_name) self.dbcon.Session["AVALON_PROJECT"] = project_name self.log.debug("Project is set to {}".format(project_name)) @@ -307,7 +309,7 @@ class DeleteOldVersions(BaseAction): dir_paths = {} file_paths_by_dir = collections.defaultdict(list) for repre in repres: - file_path, seq_path = self.path_from_represenation(repre) + file_path, seq_path = self.path_from_represenation(repre, anatomy) if file_path is None: self.log.warning(( "Could not format path for represenation \"{}\"" @@ -495,21 +497,17 @@ class DeleteOldVersions(BaseAction): self.log.debug("Removed folder: {}".format(dir_path)) os.rmdir(dir_path) - def path_from_represenation(self, representation): + def path_from_represenation(self, representation, anatomy): try: template = representation["data"]["template"] except KeyError: return (None, None) - root = os.environ["AVALON_PROJECTS"] - if not root: - return (None, None) - sequence_path = None try: context = representation["context"] - context["root"] = root + context["root"] = anatomy.roots path = avalon.pipeline.format_template_with_optional_keys( context, template ) From 4822fc88bcadbeb769923119fcc4f0ec8710aabf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 14:10:57 +0200 Subject: [PATCH 37/78] create project structure will work as before for all roots --- .../action_create_project_structure.py | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/pype/ftrack/actions/action_create_project_structure.py b/pype/ftrack/actions/action_create_project_structure.py index 163ac4836e..d418a2e623 100644 --- a/pype/ftrack/actions/action_create_project_structure.py +++ b/pype/ftrack/actions/action_create_project_structure.py @@ -32,11 +32,22 @@ class CreateProjectFolders(BaseAction): def launch(self, session, entities, event): entity = entities[0] project = self.get_project_from_entity(entity) - presets = config.get_presets()["tools"]["project_folder_structure"] + project_folder_presets = ( + config.get_presets() + .get("tools", {}) + .get("project_folder_structure") + ) + if not project_folder_presets: + return { + "success": False, + "message": "Project structure presets are not set." + } + try: # Get paths based on presets - basic_paths = self.get_path_items(presets) - self.create_folders(basic_paths, entity) + basic_paths = self.get_path_items(project_folder_presets) + anatomy = Anatomy(project["full_name"]) + self.create_folders(basic_paths, entity, project, anatomy) self.create_ftrack_entities(basic_paths, project) except Exception as exc: @@ -169,21 +180,22 @@ class CreateProjectFolders(BaseAction): output.append(os.path.normpath(os.path.sep.join(clean_items))) return output - def create_folders(self, basic_paths, entity): - # Set project root folder - if entity.entity_type.lower() == 'project': - project_name = entity['full_name'] + def create_folders(self, basic_paths, entity, project, anatomy): + roots_paths = [] + if isinstance(anatomy.roots, dict): + for root in anatomy.roots: + roots_paths.append(root.value) else: - project_name = entity['project']['full_name'] - project_root_items = [os.environ['AVALON_PROJECTS'], project_name] - project_root = os.path.sep.join(project_root_items) + roots_paths.append(anatomy.roots.value) - 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)) + for root_path in roots_paths: + project_root = os.path.join(root_path, project["full_name"]) + 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)) def register(session, plugins_presets={}): From 57b33ac9870bf1d56082c3185eca1878ec3ddf48 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 19:42:46 +0200 Subject: [PATCH 38/78] fixed few anatomy bugs --- pype/ftrack/lib/avalon_sync.py | 4 ---- pype/plugins/global/publish/integrate_new.py | 5 +---- pype/plugins/global/publish/submit_publish_job.py | 12 +++--------- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index 6f928914bf..863f447979 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -1711,14 +1711,10 @@ class SyncEntitiesFactory: except InvalidId: new_id = ObjectId() - project_name = self.entities_dict[self.ft_project_id]["name"] project_item["_id"] = new_id project_item["parent"] = None project_item["schema"] = EntitySchemas["project"] project_item["config"]["schema"] = EntitySchemas["config"] - project_item["config"]["template"] = ( - get_avalon_project_template(project_name) - ) self.ftrack_avalon_mapper[self.ft_project_id] = new_id self.avalon_ftrack_mapper[new_id] = self.ft_project_id diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 6dab0846d1..bd5e9f25f4 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -645,11 +645,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): else: source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] - root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( - anatomy.roots.find_root_template_from_path( - source, root_name, others_on_fail=True - ) + anatomy.roots_obj.find_root_template_from_path(source) ) if success: source = rootless_path diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 8525657b21..e366555088 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -185,9 +185,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): anatomy = instance.context.data["anatomy"] work_root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( - anatomy.roots.find_root_template_from_path( - output_dir, work_root_name - ) + anatomy.roots_obj.find_root_template_from_path(output_dir) ) if not success: # `rootless_path` is not set to `output_dir` if none of roots match @@ -568,9 +566,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): anatomy = instance.context.data["anatomy"] work_root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( - anatomy.roots.find_root_template_from_path( - source, work_root_name - ) + anatomy.roots_obj.find_root_template_from_path(source) ) if success: source = rootless_path @@ -622,9 +618,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): staging_dir = repre.get("stagingDir") if staging_dir: success, rootless_staging_dir = ( - anatomy.roots.find_root_template_from_path( - repre, work_root_name - ) + anatomy.roots.find_root_template_from_path(staging_dir) ) if success: repre["stagingDir"] = rootless_staging_dir From 891b14caf9d10e13aec1ec5a76f1626f3a1260c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 19:53:05 +0200 Subject: [PATCH 39/78] template is not required in project config --- schema/config-1.0.json | 1 - 1 file changed, 1 deletion(-) diff --git a/schema/config-1.0.json b/schema/config-1.0.json index b3c4362f41..198f51e04d 100644 --- a/schema/config-1.0.json +++ b/schema/config-1.0.json @@ -8,7 +8,6 @@ "additionalProperties": false, "required": [ - "template", "tasks", "apps" ], From 98548aabd2f3064c3e7a0923a38ebbe081471fbc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Apr 2020 20:27:59 +0200 Subject: [PATCH 40/78] it is possible to have set specific root instead of thumbnail_root in thumbnail template --- .../action_store_thumbnails_to_avalon.py | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/pype/ftrack/actions/action_store_thumbnails_to_avalon.py b/pype/ftrack/actions/action_store_thumbnails_to_avalon.py index 7adc36f4b5..a8fc8cb06f 100644 --- a/pype/ftrack/actions/action_store_thumbnails_to_avalon.py +++ b/pype/ftrack/actions/action_store_thumbnails_to_avalon.py @@ -54,41 +54,6 @@ class StoreThumbnailsToAvalon(BaseAction): }) session.commit() - thumbnail_roots = os.environ.get(self.thumbnail_key) - if not thumbnail_roots: - msg = "`{}` environment is not set".format(self.thumbnail_key) - - action_job["status"] = "failed" - session.commit() - - self.log.warning(msg) - - return { - "success": False, - "message": msg - } - - existing_thumbnail_root = None - for path in thumbnail_roots.split(os.pathsep): - if os.path.exists(path): - existing_thumbnail_root = path - break - - if existing_thumbnail_root is None: - msg = ( - "Can't access paths, set in `{}` ({})" - ).format(self.thumbnail_key, thumbnail_roots) - - action_job["status"] = "failed" - session.commit() - - self.log.warning(msg) - - return { - "success": False, - "message": msg - } - project = get_project_from_entity(entities[0]) project_name = project["full_name"] anatomy = Anatomy(project_name) @@ -122,6 +87,44 @@ class StoreThumbnailsToAvalon(BaseAction): "message": msg } + thumbnail_roots = os.environ.get(self.thumbnail_key) + if ( + "{thumbnail_root}" in anatomy.templates["publish"]["thumbnail"] + and not thumbnail_roots + ): + msg = "`{}` environment is not set".format(self.thumbnail_key) + + action_job["status"] = "failed" + session.commit() + + self.log.warning(msg) + + return { + "success": False, + "message": msg + } + + existing_thumbnail_root = None + for path in thumbnail_roots.split(os.pathsep): + if os.path.exists(path): + existing_thumbnail_root = path + break + + if existing_thumbnail_root is None: + msg = ( + "Can't access paths, set in `{}` ({})" + ).format(self.thumbnail_key, thumbnail_roots) + + action_job["status"] = "failed" + session.commit() + + self.log.warning(msg) + + return { + "success": False, + "message": msg + } + example_template_data = { "_id": "ID", "thumbnail_root": "THUBMNAIL_ROOT", From 32d47db29813a3aa79753892da72193eb08e0df7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Apr 2020 10:35:37 +0200 Subject: [PATCH 41/78] removed unused part of code in submit publish job --- pype/plugins/global/publish/submit_publish_job.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index adc061ee13..7b6730f983 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -183,7 +183,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): output_dir = instance.data["outputDir"] # Convert output dir to `{root}/rest/of/path/...` with Anatomy anatomy = instance.context.data["anatomy"] - work_root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( anatomy.roots_obj.find_root_template_from_path(output_dir) ) @@ -194,10 +193,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): " This may cause issues on farm." ).format(output_dir)) rootless_path = output_dir - else: - # If root was found then use `mount` root for `output_dir` - anatomy.roots._root_type = "mount" - output_dir = rootless_path.format(**{"root": anatomy.roots}) # Generate the payload for Deadline submission payload = { From 37d70cfbcecf9e42fe15c12aa36cb9f967a1556f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Apr 2020 09:29:42 +0200 Subject: [PATCH 42/78] removed unsused line --- pype/plugins/global/publish/submit_publish_job.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 7b6730f983..7158289e77 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -561,7 +561,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): source = context.data["currentFile"] anatomy = instance.context.data["anatomy"] - work_root_name = anatomy.templates["work"].get("root_name") success, rootless_path = ( anatomy.roots_obj.find_root_template_from_path(source) ) From 5724b36407f8c16f2e1fb3c8c7832acc214167c7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Apr 2020 10:39:11 +0200 Subject: [PATCH 43/78] fix roots usage --- pype/plugins/global/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 7158289e77..6d04c8cb01 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -627,7 +627,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): staging_dir = repre.get("stagingDir") if staging_dir: success, rootless_staging_dir = ( - anatomy.roots.find_root_template_from_path(staging_dir) + anatomy.roots_obj.find_root_template_from_path(staging_dir) ) if success: repre["stagingDir"] = rootless_staging_dir From 688201ca1ef2fc7326d7db7fb21c3627d2c48d68 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Apr 2020 18:08:28 +0200 Subject: [PATCH 44/78] collect rendered files use only path remapper --- .../global/publish/collect_rendered_files.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 5197f71a46..82c1b5bfd0 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -85,19 +85,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): representations = [] for repre_data in instance_data.get("representations") or []: staging_dir = repre_data.get("stagingDir") - if not staging_dir: - pass - - elif "{root" in staging_dir: - repre_data["stagingDir"] = staging_dir.format( - **{"root": anatomy.roots} - ) - self.log.debug(( - "stagingDir was filled with root." - " To: \"{}\" From: \"{}\"" - ).format(repre_data["stagingDir"], staging_dir)) - - else: + if staging_dir: remapped = anatomy.roots_obj.path_remapper(staging_dir) if remapped: repre_data["stagingDir"] = remapped From b68f049a7cae8fbd08510185ffcbbad74688819e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 30 Apr 2020 17:55:46 +0200 Subject: [PATCH 45/78] removed usage and setting of "anatomy_template" key in representations --- pype/nuke/lib.py | 1 - pype/plugins/global/publish/extract_burnin.py | 1 - pype/plugins/global/publish/submit_publish_job.py | 6 +----- pype/plugins/maya/publish/extract_yeti_cache.py | 4 +--- pype/plugins/maya/publish/extract_yeti_rig.py | 6 ++---- pype/plugins/nuke/publish/collect_writes.py | 3 +-- pype/plugins/nuke/publish/extract_render_local.py | 3 +-- pype/plugins/nuke/publish/extract_thumbnail.py | 1 - .../publish/collect_context.py | 15 +-------------- 9 files changed, 7 insertions(+), 33 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 423738dd7f..249a3f8f5b 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -1412,7 +1412,6 @@ class ExporterReview: 'ext': self.ext, 'files': self.file, "stagingDir": self.staging_dir, - "anatomy_template": "render", "tags": [self.name.replace("_", "-")] + add_tags } diff --git a/pype/plugins/global/publish/extract_burnin.py b/pype/plugins/global/publish/extract_burnin.py index c151752c8f..7668eafd2a 100644 --- a/pype/plugins/global/publish/extract_burnin.py +++ b/pype/plugins/global/publish/extract_burnin.py @@ -193,7 +193,6 @@ class ExtractBurnin(pype.api.Extractor): self.log.debug("Output: {}".format(output)) repre_update = { - "anatomy_template": "render", "files": movieFileBurnin, "name": repre["name"], "tags": [x for x in repre["tags"] if x != "delete"] diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 843760f9ec..b9c14d4fe4 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -380,7 +380,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "frameEnd": int(instance_data.get("frameEndHandle")), # If expectedFile are absolute, we need only filenames "stagingDir": staging, - "anatomy_template": "render", "fps": new_instance.get("fps"), "tags": ["review"] if preview else [] } @@ -443,7 +442,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "frameEnd": int(instance.get("frameEndHandle")), # If expectedFile are absolute, we need only filenames "stagingDir": os.path.dirname(list(c)[0]), - "anatomy_template": "render", "fps": instance.get("fps"), "tags": ["review", "preview"] if preview else [], } @@ -462,13 +460,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "name": ext, "ext": ext, "files": os.path.basename(r), - "stagingDir": os.path.dirname(r), - "anatomy_template": "publish" + "stagingDir": os.path.dirname(r) } if r in bake_render_path: rep.update({ "fps": instance.get("fps"), - "anatomy_template": "render", "tags": ["review", "delete"] }) # solve families with `preview` attributes diff --git a/pype/plugins/maya/publish/extract_yeti_cache.py b/pype/plugins/maya/publish/extract_yeti_cache.py index 7d85f396ae..5a67a6ab7e 100644 --- a/pype/plugins/maya/publish/extract_yeti_cache.py +++ b/pype/plugins/maya/publish/extract_yeti_cache.py @@ -73,7 +73,6 @@ class ExtractYetiCache(pype.api.Extractor): 'ext': 'fur', 'files': cache_files[0] if len(cache_files) == 1 else cache_files, 'stagingDir': dirname, - 'anatomy_template': 'publish', 'frameStart': int(start_frame), 'frameEnd': int(end_frame) } @@ -84,8 +83,7 @@ class ExtractYetiCache(pype.api.Extractor): 'name': 'fursettings', 'ext': 'fursettings', 'files': os.path.basename(data_file), - 'stagingDir': dirname, - 'anatomy_template': 'publish' + 'stagingDir': dirname } ) diff --git a/pype/plugins/maya/publish/extract_yeti_rig.py b/pype/plugins/maya/publish/extract_yeti_rig.py index 98e7271d1a..f82cd75c30 100644 --- a/pype/plugins/maya/publish/extract_yeti_rig.py +++ b/pype/plugins/maya/publish/extract_yeti_rig.py @@ -169,8 +169,7 @@ class ExtractYetiRig(pype.api.Extractor): 'name': "ma", 'ext': 'ma', 'files': "yeti_rig.ma", - 'stagingDir': dirname, - 'anatomy_template': 'publish' + 'stagingDir': dirname } ) self.log.info("settings file: {}".format("yeti.rigsettings")) @@ -179,8 +178,7 @@ class ExtractYetiRig(pype.api.Extractor): 'name': 'rigsettings', 'ext': 'rigsettings', 'files': 'yeti.rigsettings', - 'stagingDir': dirname, - 'anatomy_template': 'publish' + 'stagingDir': dirname } ) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index 6379a1db87..1850df2d00 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -79,8 +79,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): representation = { 'name': ext, 'ext': ext, - "stagingDir": output_dir, - "anatomy_template": "render" + "stagingDir": output_dir } try: diff --git a/pype/plugins/nuke/publish/extract_render_local.py b/pype/plugins/nuke/publish/extract_render_local.py index b7aa59a457..37a6701380 100644 --- a/pype/plugins/nuke/publish/extract_render_local.py +++ b/pype/plugins/nuke/publish/extract_render_local.py @@ -71,8 +71,7 @@ class NukeRenderLocal(pype.api.Extractor): 'ext': ext, 'frameStart': "%0{}d".format(len(str(last_frame))) % first_frame, 'files': collected_frames, - "stagingDir": out_dir, - "anatomy_template": "render" + "stagingDir": out_dir } instance.data["representations"].append(repre) diff --git a/pype/plugins/nuke/publish/extract_thumbnail.py b/pype/plugins/nuke/publish/extract_thumbnail.py index 362625c2f5..5e9302a01a 100644 --- a/pype/plugins/nuke/publish/extract_thumbnail.py +++ b/pype/plugins/nuke/publish/extract_thumbnail.py @@ -130,7 +130,6 @@ class ExtractThumbnail(pype.api.Extractor): "stagingDir": staging_dir, "frameStart": first_frame, "frameEnd": last_frame, - "anatomy_template": "render", "tags": tags } instance.data["representations"].append(repre) diff --git a/pype/plugins/standalonepublisher/publish/collect_context.py b/pype/plugins/standalonepublisher/publish/collect_context.py index 327b99f432..0567f82755 100644 --- a/pype/plugins/standalonepublisher/publish/collect_context.py +++ b/pype/plugins/standalonepublisher/publish/collect_context.py @@ -46,7 +46,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): in_data = json.load(f) asset_name = in_data["asset"] - family_preset_key = in_data.get("family_preset_key", "") family = in_data["family"] subset = in_data["subset"] @@ -57,15 +56,6 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): presets = config.get_presets() - # Get from presets anatomy key that will be used for getting template - # - default integrate new is used if not set - anatomy_key = ( - presets.get("standalone_publish", {}) - .get("families", {}) - .get(family_preset_key, {}) - .get("anatomy_template") - ) - project = io.find_one({"type": "project"}) asset = io.find_one({"type": "asset", "name": asset_name}) context.data["project"] = project @@ -98,12 +88,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): instance.data["source"] = "standalone publisher" for component in in_data["representations"]: - component["destination"] = component["files"] component["stagingDir"] = component["stagingDir"] - # Do not set anatomy_template if not specified - if anatomy_key: - component["anatomy_template"] = anatomy_key + if isinstance(component["files"], list): collections, remainder = clique.assemble(component["files"]) self.log.debug("collecting sequence: {}".format(collections)) From ebdfaf8baaf6caed014aa9f7867b61364eb17d70 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 30 Apr 2020 19:35:03 +0200 Subject: [PATCH 46/78] implemented method for template name determination based on plugin presets --- pype/plugins/global/publish/integrate_new.py | 76 ++++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/pype/plugins/global/publish/integrate_new.py b/pype/plugins/global/publish/integrate_new.py index 9eab1a15b1..d26cc4e856 100644 --- a/pype/plugins/global/publish/integrate_new.py +++ b/pype/plugins/global/publish/integrate_new.py @@ -40,10 +40,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): 'name': representation name (usually the same as extension) 'ext': file extension optional data - 'anatomy_template': 'publish' or 'render', etc. - template from anatomy that should be used for - integrating this file. Only the first level can - be specified right now. "frameStart" "frameEnd" 'fps' @@ -92,6 +88,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "family", "hierarchy", "task", "username" ] default_template_name = "publish" + template_name_profiles = None def process(self, instance): @@ -268,6 +265,8 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if 'transfers' not in instance.data: instance.data['transfers'] = [] + template_name = self.template_name_from_instance(instance) + published_representations = {} for idx, repre in enumerate(instance.data["representations"]): published_files = [] @@ -292,9 +291,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): if repre.get('stagingDir'): stagingdir = repre['stagingDir'] - template_name = ( - repre.get('anatomy_template') or self.default_template_name - ) if repre.get("outputName"): template_data["output"] = repre['outputName'] @@ -701,3 +697,69 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): version_data[key] = instance.data[key] return version_data + + def main_family_from_instance(self, instance): + """Returns main family of entered instance.""" + family = instance.data.get("family") + if not family: + family = instance.data["families"][0] + return family + + def template_name_from_instance(self, instance): + template_name = self.default_template_name + if not self.template_name_profiles: + self.log.debug(( + "Template name profiles are not set." + " Using default \"{}\"" + ).format(template_name)) + return template_name + + # Task name from session? + task_name = io.Session.get("AVALON_TASK") + family = self.main_family_from_instance(instance) + + matching_profiles = None + highest_value = -1 + for name, filters in self.template_name_profiles: + value = 0 + families = filters.get("families") + if families: + if family not in families: + continue + value += 1 + + tasks = filters.get("tasks") + if tasks: + if task_name not in tasks: + continue + value += 1 + + if value > highest_value: + matching_profiles = {} + highest_value = value + + if value == highest_value: + matching_profiles[name] = filters + + if len(matching_profiles) == 1: + template_name = matching_profiles.keys()[0] + self.log.debug( + "Using template name \"{}\".".format(template_name) + ) + + elif len(matching_profiles) > 1: + template_name = matching_profiles.keys()[0] + self.log.warning(( + "More than one template profiles matched" + " Family \"{}\" and Task: \"{}\"." + " Using first template name in row \"{}\"." + ).format(family, task_name, template_name)) + + else: + self.log.debug(( + "None of template profiles matched" + " Family \"{}\" and Task: \"{}\"." + " Using default template name \"{}\"" + ).format(family, task_name, template_name)) + + return template_name From 87a000227af72854798e19427536b45669f93226 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 May 2020 19:23:01 +0200 Subject: [PATCH 47/78] sync to avalon ignores MongoID attribute in hierarchical attributes --- pype/ftrack/events/event_sync_to_avalon.py | 2 ++ pype/ftrack/lib/avalon_sync.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pype/ftrack/events/event_sync_to_avalon.py b/pype/ftrack/events/event_sync_to_avalon.py index faf7539540..71e52c68da 100644 --- a/pype/ftrack/events/event_sync_to_avalon.py +++ b/pype/ftrack/events/event_sync_to_avalon.py @@ -1244,6 +1244,8 @@ class SyncToAvalonEvent(BaseEvent): self.process_session, entity, hier_keys, defaults ) for key, val in hier_values.items(): + if key == CustAttrIdKey: + continue output[key] = val return output diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index 474c70bd26..179977d403 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -877,6 +877,8 @@ class SyncEntitiesFactory: project_values[key] = value for key in avalon_hier: + if key == CustAttrIdKey: + continue value = self.entities_dict[top_id]["avalon_attrs"][key] if value is not None: project_values[key] = value From 25b2c66ebee62d0e7de33e34ed997c23514b3d5c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 May 2020 19:26:42 +0200 Subject: [PATCH 48/78] security roles are queried effectivelly --- .../actions/action_create_cust_attrs.py | 78 +++++++++---------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index 5279a95a20..ff3a30b534 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -138,7 +138,7 @@ class CustomAttributes(BaseAction): self.types = {} self.object_type_ids = {} self.groups = {} - self.security_roles = {} + self.security_roles = None # JOB SETTINGS userId = event['source']['user']['id'] @@ -199,8 +199,8 @@ class CustomAttributes(BaseAction): filtered_types_id.add(obj_type['id']) # Set security roles for attribute - role_list = ['API', 'Administrator'] - roles = self.get_security_role(role_list) + role_list = ("API", "Administrator", "Pypeclub") + roles = self.get_security_roles(role_list) # Set Text type of Attribute custom_attribute_type = self.get_type('text') # Set group to 'avalon' @@ -416,48 +416,40 @@ class CustomAttributes(BaseAction): 'Found more than one group "{}"'.format(group_name) ) - def get_role_ALL(self): - role_name = 'ALL' - if role_name in self.security_roles: - all_roles = self.security_roles[role_name] - else: - all_roles = self.session.query('SecurityRole').all() - self.security_roles[role_name] = all_roles - for role in all_roles: - if role['name'] not in self.security_roles: - self.security_roles[role['name']] = role - return all_roles + def query_roles(self): + if self.security_roles is None: + self.security_roles = {} + for role in self.session.query("SecurityRole").all(): + key = role["name"].lower() + self.security_roles[key] = role + return self.security_roles - def get_security_role(self, security_roles): - roles = [] - security_roles_lowered = [role.lower() for role in security_roles] - if len(security_roles) == 0 or 'all' in security_roles_lowered: - roles = self.get_role_ALL() - elif security_roles_lowered[0] == 'except': - excepts = security_roles[1:] - all = self.get_role_ALL() - for role in all: - if role['name'] not in excepts: - roles.append(role) - if role['name'] not in self.security_roles: - self.security_roles[role['name']] = role - else: - for role_name in security_roles: - if role_name in self.security_roles: - roles.append(self.security_roles[role_name]) - continue + def get_security_roles(self, security_roles): + security_roles = self.query_roles() - try: - query = 'SecurityRole where name is "{}"'.format(role_name) - role = self.session.query(query).one() - self.security_roles[role_name] = role - roles.append(role) - except NoResultFoundError: + security_roles_lowered = tuple(name.lower() for name in security_roles) + if ( + len(security_roles_lowered) == 0 + or "all" in security_roles_lowered + ): + return tuple(security_roles.values()) + + output = [] + if security_roles_lowered[0] == "except": + excepts = security_roles_lowered[1:] + for role_name, role in security_roles.items(): + if role_name not in excepts: + output.append(role) + + else: + for role_name in security_roles_lowered: + if role_name in security_roles: + output.append(security_roles[role_name]) + else: raise CustAttrException(( - 'Securit role "{}" does not exist' + "Securit role \"{}\" was not found in Ftrack." ).format(role_name)) - - return roles + return output def get_default(self, attr): type = attr['type'] @@ -512,8 +504,8 @@ class CustomAttributes(BaseAction): roles_read = attr['read_security_roles'] if 'read_security_roles' in output: roles_write = attr['write_security_roles'] - output['read_security_roles'] = self.get_security_role(roles_read) - output['write_security_roles'] = self.get_security_role(roles_write) + output['read_security_roles'] = self.get_security_roles(roles_read) + output['write_security_roles'] = self.get_security_roles(roles_write) return output From 83352891585c3b4f567585d3f66a43d30b1de7ec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 May 2020 19:27:03 +0200 Subject: [PATCH 49/78] few minor possible bugs fix --- pype/ftrack/actions/action_create_cust_attrs.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index ff3a30b534..8ee1d138e3 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -8,7 +8,6 @@ import ftrack_api from pype.ftrack import BaseAction from pype.ftrack.lib.avalon_sync import CustAttrIdKey from pypeapp import config -from ftrack_api.exception import NoResultFoundError """ This action creates/updates custom attributes. @@ -382,15 +381,15 @@ class CustomAttributes(BaseAction): config = json.dumps({ 'multiSelect': multiSelect, 'data': json.dumps(data) - }) + }) return config def get_group(self, attr): - if isinstance(attr, str): - group_name = attr - else: + if isinstance(attr, dict): group_name = attr['group'].lower() + else: + group_name = attr if group_name in self.groups: return self.groups[group_name] From 1f2451df7377aa328f7984ee96167f28505baa23 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 May 2020 19:36:40 +0200 Subject: [PATCH 50/78] added method for converting mongoid attr from per entity type to hierarchical attribute --- .../actions/action_create_cust_attrs.py | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index 8ee1d138e3..12e40b7ee5 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -224,6 +224,94 @@ class CustomAttributes(BaseAction): data['object_type_id'] = str(object_type_id) self.process_attribute(data) + def convert_mongo_id_to_hierarchical( + self, hierarchical_attr, object_type_attrs, session, event + ): + user_msg = "Converting old custom attributes. This may take some time." + self.show_message(event, user_msg, True) + self.log.info(user_msg) + + object_types_per_id = { + object_type["id"]: object_type + for object_type in session.query("ObjectType").all() + } + + cust_attr_query = ( + "select value, entity_id from ContextCustomAttributeValue " + "where configuration_id is {}" + ) + for attr_def in object_type_attrs: + attr_ent_type = attr_def["entity_type"] + if attr_ent_type == "show": + entity_type_label = "Project" + elif attr_ent_type == "task": + entity_type_label = ( + object_types_per_id[attr_def["object_type_id"]] + ) + else: + self.log.warning( + "Unsupported entity type: \"{}\". Skipping.".format( + attr_ent_type + ) + ) + continue + + self.log.debug(( + "Converting Avalon MongoID attr for Entity type \"{}\"." + ).format(entity_type_label)) + + call_expr = [{ + "action": "query", + "expression": cust_attr_query.format(attr_def["id"]) + }] + if hasattr(session, "call"): + [values] = session.call(call_expr) + else: + [values] = session._call(call_expr) + + for value in values["data"]: + table_values = collections.OrderedDict({ + "configuration_id": hierarchical_attr["id"], + "entity_id": value["entity_id"] + }) + + session.recorded_operations.push( + ftrack_api.operation.UpdateEntityOperation( + "ContextCustomAttributeValue", + table_values, + "value", + ftrack_api.symbol.NOT_SET, + value["value"] + ) + ) + + try: + session.commit() + + except Exception: + session.rollback() + self.log.warning( + ( + "Couldn't transfer Avalon Mongo ID" + " attribute for entity type \"{}\"." + ).format(entity_type_label), + exc_info=True + ) + + try: + session.delete(attr_def) + session.commit() + + except Exception: + session.rollback() + self.log.warning( + ( + "Couldn't delete Avalon Mongo ID" + " attribute for entity type \"{}\"." + ).format(entity_type_label), + exc_info=True + ) + def custom_attributes_from_file(self, session, event): presets = config.get_presets()['ftrack']['ftrack_custom_attributes'] From f345da371520b96ba6cc021fdaea224510d66776 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 May 2020 19:47:19 +0200 Subject: [PATCH 51/78] removed standalone action support --- .../actions/action_create_cust_attrs.py | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index 12e40b7ee5..2ff0a3b49d 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -1,9 +1,6 @@ import os -import sys -import argparse import json import arrow -import logging import ftrack_api from pype.ftrack import BaseAction from pype.ftrack.lib.avalon_sync import CustAttrIdKey @@ -652,42 +649,3 @@ def register(session, plugins_presets={}): '''Register plugin. Called when used as an plugin.''' CustomAttributes(session, plugins_presets).register() - - -def main(arguments=None): - '''Set up logging and register action.''' - if arguments is None: - arguments = [] - - parser = argparse.ArgumentParser() - # Allow setting of logging level from arguments. - loggingLevels = {} - for level in ( - logging.NOTSET, logging.DEBUG, logging.INFO, logging.WARNING, - logging.ERROR, logging.CRITICAL - ): - loggingLevels[logging.getLevelName(level).lower()] = level - - parser.add_argument( - '-v', '--verbosity', - help='Set the logging output verbosity.', - choices=loggingLevels.keys(), - default='info' - ) - namespace = parser.parse_args(arguments) - - # Set up basic logging - logging.basicConfig(level=loggingLevels[namespace.verbosity]) - - session = ftrack_api.Session() - register(session) - - # Wait for events - logging.info( - 'Registered actions and listening for events. Use Ctrl-C to abort.' - ) - session.event_hub.wait() - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) From 9935972a859531014d81b7c71efc69a2c649ff05 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 May 2020 19:54:12 +0200 Subject: [PATCH 52/78] hierarchical mongo id attribute automatically replace previous access --- .../actions/action_create_cust_attrs.py | 201 +++++++++--------- 1 file changed, 99 insertions(+), 102 deletions(-) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index 2ff0a3b49d..c141d6672c 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -1,4 +1,5 @@ import os +import collections import json import arrow import ftrack_api @@ -131,11 +132,6 @@ class CustomAttributes(BaseAction): return True def launch(self, session, entities, event): - self.types = {} - self.object_type_ids = {} - self.groups = {} - self.security_roles = None - # JOB SETTINGS userId = event['source']['user']['id'] user = session.query('User where id is ' + userId).one() @@ -149,7 +145,8 @@ class CustomAttributes(BaseAction): }) session.commit() try: - self.avalon_mongo_id_attributes(session) + self.prepare_global_data(session) + self.avalon_mongo_id_attributes(session, event) self.custom_attributes_from_file(session, event) job['status'] = 'done' @@ -166,60 +163,92 @@ class CustomAttributes(BaseAction): return True - def avalon_mongo_id_attributes(self, session): + def prepare_global_data(self, session): + self.types_per_name = { + attr_type["name"].lower(): attr_type + for attr_type in session.query("CustomAttributeType").all() + } + + self.security_roles = { + role["name"].lower(): role + for role in session.query("SecurityRole").all() + } + + object_types = session.query("ObjectType").all() + self.object_types_per_id = { + object_type["id"]: object_type for object_type in object_types + } + self.object_types_per_name = { + object_type["name"].lower(): object_type + for object_type in object_types + } + + self.groups = {} + + def avalon_mongo_id_attributes(self, session, event): + hierarchical_attr, object_type_attrs = ( + self.mongo_id_custom_attributes(session) + ) + + if hierarchical_attr is None: + self.create_hierarchical_mongo_attr(session) + hierarchical_attr, object_type_attrs = ( + self.mongo_id_custom_attributes(session) + ) + + if hierarchical_attr is None: + return + + if object_type_attrs: + self.convert_mongo_id_to_hierarchical( + hierarchical_attr, object_type_attrs, session, event + ) + + def mongo_id_custom_attributes(self, session): + cust_attrs_query = ( + "select id, entity_type, object_type_id, is_hierarchical, default" + " from CustomAttributeConfiguration" + " where key = \"{}\"" + ).format(CustAttrIdKey) + + mongo_id_avalon_attr = session.query(cust_attrs_query).all() + heirarchical_attr = None + object_type_attrs = [] + for cust_attr in mongo_id_avalon_attr: + if cust_attr["is_hierarchical"]: + heirarchical_attr = cust_attr + + else: + object_type_attrs.append(cust_attr) + + return heirarchical_attr, object_type_attrs + + def create_hierarchical_mongo_attr(self, session): # Attribute Name and Label - cust_attr_label = 'Avalon/Mongo Id' - - # Types that don't need object_type_id - base = {'show'} - - # Don't create custom attribute on these entity types: - exceptions = ['task', 'milestone'] - exceptions.extend(base) - - # Get all possible object types - all_obj_types = session.query('ObjectType').all() - - # Filter object types by exceptions - filtered_types_id = set() - - for obj_type in all_obj_types: - name = obj_type['name'] - if " " in name: - name = name.replace(' ', '') - - if obj_type['name'] not in self.object_type_ids: - self.object_type_ids[name] = obj_type['id'] - - if name.lower() not in exceptions: - filtered_types_id.add(obj_type['id']) + cust_attr_label = "Avalon/Mongo ID" # Set security roles for attribute role_list = ("API", "Administrator", "Pypeclub") roles = self.get_security_roles(role_list) # Set Text type of Attribute - custom_attribute_type = self.get_type('text') + custom_attribute_type = self.types_per_name["text"] # Set group to 'avalon' - group = self.get_group('avalon') + group = self.get_group("avalon") - data = {} - data['key'] = CustAttrIdKey - data['label'] = cust_attr_label - data['type'] = custom_attribute_type - data['default'] = '' - data['write_security_roles'] = roles - data['read_security_roles'] = roles - data['group'] = group - data['config'] = json.dumps({'markdown': False}) + data = { + "key": CustAttrIdKey, + "label": cust_attr_label, + "type": custom_attribute_type, + "default": "", + "write_security_roles": roles, + "read_security_roles": roles, + "group": group, + "is_hierarchical": True, + "entity_type": "show", + "config": json.dumps({"markdown": False}) + } - for entity_type in base: - data['entity_type'] = entity_type - self.process_attribute(data) - - data['entity_type'] = 'task' - for object_type_id in filtered_types_id: - data['object_type_id'] = str(object_type_id) - self.process_attribute(data) + self.process_attribute(data) def convert_mongo_id_to_hierarchical( self, hierarchical_attr, object_type_attrs, session, event @@ -401,11 +430,11 @@ class CustomAttributes(BaseAction): 'Type {} is not valid'.format(attr['type']) ) - type_name = attr['type'].lower() - output['key'] = attr['key'] output['label'] = attr['label'] - output['type'] = self.get_type(type_name) + + type_name = attr['type'].lower() + output['type'] = self.types_per_name[type_name] config = None if type_name == 'number': @@ -500,35 +529,25 @@ class CustomAttributes(BaseAction): 'Found more than one group "{}"'.format(group_name) ) - def query_roles(self): - if self.security_roles is None: - self.security_roles = {} - for role in self.session.query("SecurityRole").all(): - key = role["name"].lower() - self.security_roles[key] = role - return self.security_roles - def get_security_roles(self, security_roles): - security_roles = self.query_roles() - security_roles_lowered = tuple(name.lower() for name in security_roles) if ( len(security_roles_lowered) == 0 or "all" in security_roles_lowered ): - return tuple(security_roles.values()) + return list(self.security_roles.values()) output = [] if security_roles_lowered[0] == "except": excepts = security_roles_lowered[1:] - for role_name, role in security_roles.items(): + for role_name, role in self.security_roles.items(): if role_name not in excepts: output.append(role) else: for role_name in security_roles_lowered: - if role_name in security_roles: - output.append(security_roles[role_name]) + if role_name in self.security_roles: + output.append(self.security_roles[role_name]) else: raise CustAttrException(( "Securit role \"{}\" was not found in Ftrack." @@ -593,27 +612,12 @@ class CustomAttributes(BaseAction): return output - def get_type(self, type_name): - if type_name in self.types: - return self.types[type_name] - - query = 'CustomAttributeType where name is "{}"'.format(type_name) - type = self.session.query(query).one() - self.types[type_name] = type - - return type - def get_entity_type(self, attr): - if 'is_hierarchical' in attr: - if attr['is_hierarchical'] is True: - type = 'show' - if 'entity_type' in attr: - type = attr['entity_type'] - - return { - 'is_hierarchical': True, - 'entity_type': type - } + if attr.get("is_hierarchical", False): + return { + "is_hierarchical": True, + "entity_type": attr.get("entity_type") or "show" + } if 'entity_type' not in attr: raise CustAttrException('Missing entity_type') @@ -625,23 +629,16 @@ class CustomAttributes(BaseAction): raise CustAttrException('Missing object_type') object_type_name = attr['object_type'] - if object_type_name not in self.object_type_ids: - try: - query = 'ObjectType where name is "{}"'.format( - object_type_name - ) - object_type_id = self.session.query(query).one()['id'] - except Exception: - raise CustAttrException(( - 'Object type with name "{}" don\'t exist' - ).format(object_type_name)) - self.object_type_ids[object_type_name] = object_type_id - else: - object_type_id = self.object_type_ids[object_type_name] + object_type_name_low = object_type_name.lower() + object_type = self.object_types_per_name.get(object_type_name_low) + if not object_type: + raise CustAttrException(( + 'Object type with name "{}" don\'t exist' + ).format(object_type_name)) return { 'entity_type': attr['entity_type'], - 'object_type_id': object_type_id + 'object_type_id': object_type["id"] } From 0c73041ab402d4e0eb54d8d84c6bbfc599713632 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 4 May 2020 20:29:15 +0200 Subject: [PATCH 53/78] fixed getting configuration id with hierarchical mongo id attribute --- pype/ftrack/lib/avalon_sync.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index 179977d403..e915e86184 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -291,6 +291,8 @@ class SyncEntitiesFactory: self.filtered_ids = [] self.not_selected_ids = [] + self.hier_cust_attr_ids_by_key = {} + self._ent_paths_by_ftrack_id = {} self.ftrack_avalon_mapper = None @@ -812,6 +814,7 @@ class SyncEntitiesFactory: key = attr["key"] attribute_key_by_id[attr["id"]] = key attributes_by_key[key] = attr + self.hier_cust_attr_ids_by_key[key] = attr["id"] store_key = "hier_attrs" if key.startswith("avalon_"): @@ -1595,9 +1598,16 @@ class SyncEntitiesFactory: if current_id != new_id_str: # store mongo id to ftrack entity - configuration_id = self.entities_dict[ftrack_id][ - "avalon_attrs_id" - ][CustAttrIdKey] + configuration_id = self.hier_cust_attr_ids_by_key.get( + CustAttrIdKey + ) + if not configuration_id: + # NOTE this is for cases when CustAttrIdKey key is not + # hierarchical custom attribute but per entity type + configuration_id = self.entities_dict[ftrack_id][ + "avalon_attrs_id" + ][CustAttrIdKey] + _entity_key = collections.OrderedDict({ "configuration_id": configuration_id, "entity_id": ftrack_id From 0d30a3bc8eae857327d47f2d348a02875ad2eac3 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 4 May 2020 22:40:29 +0100 Subject: [PATCH 54/78] Expose write node frame range and limit checkbox. - also some code cosmetics. --- pype/nuke/lib.py | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 423738dd7f..185def7052 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -313,7 +313,7 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): if input: # if connected input node was defined connections.append({ - "node": input, + "node": input, "inputName": input.name()}) prev_node = nuke.createNode( "Input", "name {}".format(input.name())) @@ -369,7 +369,7 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): write_node = now_node = avalon.nuke.lib.add_write_node( "inside_{}".format(name), **_data - ) + ) # connect to previous node now_node.setInput(0, prev_node) @@ -393,11 +393,13 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): if review: add_review_knob(GN) - # add render button - lnk = nuke.Link_Knob("Render") - lnk.makeLink(write_node.name(), "Render") - lnk.setName("Render") - GN.addKnob(lnk) + # Add linked knobs. + linked_knob_names = ["Render", "use_limit", "first", "last"] + for name in linked_knob_names: + link = nuke.Link_Knob(name) + link.makeLink(write_node.name(), name) + link.setName(name) + GN.addKnob(link) divider = nuke.Text_Knob('') GN.addKnob(divider) @@ -408,7 +410,6 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): # Deadline tab. add_deadline_tab(GN) - # set tile color tile_color = _data.get("tile_color", "0xff0000ff") GN["tile_color"].setValue(tile_color) @@ -436,6 +437,7 @@ def add_rendering_knobs(node): node.addKnob(knob) return node + def add_review_knob(node): ''' Adds additional review knob to given node @@ -645,8 +647,9 @@ class WorkfileSettings(object): if root_dict.get("customOCIOConfigPath"): self._root_node["customOCIOConfigPath"].setValue( str(root_dict["customOCIOConfigPath"]).format( - **os.environ).replace("\\", "/") - ) + **os.environ + ).replace("\\", "/") + ) log.debug("nuke.root()['{}'] changed to: {}".format( "customOCIOConfigPath", root_dict["customOCIOConfigPath"])) root_dict.pop("customOCIOConfigPath") @@ -750,10 +753,9 @@ class WorkfileSettings(object): if changes: msg = "Read nodes are not set to correct colospace:\n\n" for nname, knobs in changes.items(): - msg += str(" - node: '{0}' is now '{1}' " - "but should be '{2}'\n").format( - nname, knobs["from"], knobs["to"] - ) + msg += str( + " - node: '{0}' is now '{1}' but should be '{2}'\n" + ).format(nname, knobs["from"], knobs["to"]) msg += "\nWould you like to change it?" @@ -1420,7 +1422,7 @@ class ExporterReview: repre.update({ "frameStart": self.first_frame, "frameEnd": self.last_frame, - }) + }) self.data["representations"].append(repre) @@ -1655,7 +1657,7 @@ class ExporterReviewMov(ExporterReview): if not self.viewer_lut_raw: colorspaces = [ self.bake_colorspace_main, self.bake_colorspace_fallback - ] + ] if any(colorspaces): # OCIOColorSpace with controled output @@ -1709,7 +1711,7 @@ class ExporterReviewMov(ExporterReview): self.get_representation_data( tags=["review", "delete"], range=True - ) + ) self.log.debug("Representation... `{}`".format(self.data)) @@ -1744,14 +1746,14 @@ def get_dependent_nodes(nodes): if test_in: connections_in.update({ node: test_in - }) + }) # collect all outputs outside test_out = [i for i in outputs if i.name() not in node_names] if test_out: # only one dependent node is allowed connections_out.update({ node: test_out[-1] - }) + }) return connections_in, connections_out From 738c18792830ba450536cc63d84b1f44e31c4a8b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 5 May 2020 10:00:06 +0200 Subject: [PATCH 55/78] annoying fix --- pype/ftrack/actions/action_create_cust_attrs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_create_cust_attrs.py b/pype/ftrack/actions/action_create_cust_attrs.py index c141d6672c..37b11256d2 100644 --- a/pype/ftrack/actions/action_create_cust_attrs.py +++ b/pype/ftrack/actions/action_create_cust_attrs.py @@ -272,7 +272,7 @@ class CustomAttributes(BaseAction): entity_type_label = "Project" elif attr_ent_type == "task": entity_type_label = ( - object_types_per_id[attr_def["object_type_id"]] + object_types_per_id[attr_def["object_type_id"]]["name"] ) else: self.log.warning( From 8fde8167a745ee8ee01c566433164c2fde02ff61 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 6 May 2020 17:22:20 +0200 Subject: [PATCH 56/78] event handler now set thumbnail only if new asset version is created --- pype/ftrack/events/event_thumbnail_updates.py | 72 ++++++++----------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/pype/ftrack/events/event_thumbnail_updates.py b/pype/ftrack/events/event_thumbnail_updates.py index 5421aa7543..c62be9718a 100644 --- a/pype/ftrack/events/event_thumbnail_updates.py +++ b/pype/ftrack/events/event_thumbnail_updates.py @@ -2,57 +2,42 @@ from pype.ftrack import BaseEvent class ThumbnailEvents(BaseEvent): - def launch(self, session, event): - '''just a testing event''' + """Updates thumbnails of entities from new AssetVersion.""" - # self.log.info(event) - # start of event procedure ---------------------------------- - for entity in event['data'].get('entities', []): + for entity in event["data"].get("entities", []): + if ( + entity["action"] == "remove" + or entity["entityType"].lower() != "assetversion" + or "thumbid" not in (entity.get("keys") or []) + ): + continue # update created task thumbnail with first parent thumbnail - if entity['entityType'] == 'task' and entity['action'] == 'add': + version = session.get("AssetVersion", entity["entityId"]) + if not version: + continue - task = session.get('TypedContext', entity['entityId']) - parent = task['parent'] + thumbnail = version.get("thumbnail") + if not thumbnail: + continue - if parent.get('thumbnail') and not task.get('thumbnail'): - task['thumbnail'] = parent['thumbnail'] - self.log.info('>>> Updated thumbnail on [ %s/%s ]'.format( - parent['name'], task['name'] - )) + parent = version["asset"]["parent"] + task = version["task"] + parent["thumbnail_id"] = version["thumbnail_id"] + if parent.entity_type.lower() == "project": + name = parent["full_name"] + else: + name = parent["name"] - # Update task thumbnail from published version - # if (entity['entityType'] == 'assetversion' and - # entity['action'] == 'encoded'): - elif ( - entity['entityType'] == 'assetversion' and - entity['action'] != 'remove' and - 'thumbid' in (entity.get('keys') or []) - ): + task_msg = "" + if task: + task["thumbnail_id"] = version["thumbnail_id"] + task_msg = " and task [ {} ]".format(task["name"]) - version = session.get('AssetVersion', entity['entityId']) - if not version: - continue - - thumbnail = version.get('thumbnail') - if not thumbnail: - continue - - parent = version['asset']['parent'] - task = version['task'] - parent['thumbnail_id'] = version['thumbnail_id'] - if parent.entity_type.lower() == "project": - name = parent["full_name"] - else: - name = parent["name"] - msg = '>>> Updating thumbnail for shot [ {} ]'.format(name) - - if task: - task['thumbnail_id'] = version['thumbnail_id'] - msg += " and task [ {} ]".format(task["name"]) - - self.log.info(msg) + self.log.info(">>> Updating thumbnail for shot [ {} ]{}".format( + name, task_msg + )) try: session.commit() @@ -61,5 +46,4 @@ class ThumbnailEvents(BaseEvent): def register(session, plugins_presets): - '''Register plugin. Called when used as an plugin.''' ThumbnailEvents(session, plugins_presets).register() From 76e7896e2453433ab2e473e6c5ea544e58dbd932 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 May 2020 14:42:36 +0200 Subject: [PATCH 57/78] collect rendered files also remap AVALON_WORKDIR and stagingDir on instances --- .../global/publish/collect_rendered_files.py | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 82c1b5bfd0..fab43b116a 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -27,7 +27,9 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): _context = None def _load_json(self, path): - assert os.path.isfile(path), ("path to json file doesn't exist") + assert os.path.isfile(path), ( + "Path to json file doesn't exist. \"{}\"".format(path) + ) data = None with open(path, "r") as json_file: try: @@ -39,6 +41,16 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): ) return data + def _remap_staging_dir(self, data_object, anatomy): + staging_dir = data_object.get("stagingDir") + if staging_dir: + remapped = anatomy.roots_obj.path_remapper(staging_dir) + if remapped: + data_object["stagingDir"] = remapped + self.log.debug(( + "stagingDir was remapped. To: \"{}\" From: \"{}\"" + ).format(remapped, staging_dir)) + def _process_path(self, data, anatomy): # validate basic necessary data data_err = "invalid json file - missing data" @@ -80,19 +92,13 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): instance_data.get("subset") ) self.log.info("Filling stagignDir...") + + self._remap_staging_dir(instance_data, anatomy) instance.data.update(instance_data) representations = [] for repre_data in instance_data.get("representations") or []: - staging_dir = repre_data.get("stagingDir") - if staging_dir: - remapped = anatomy.roots_obj.path_remapper(staging_dir) - if remapped: - repre_data["stagingDir"] = remapped - self.log.debug(( - "stagingDir was remapped. To: \"{}\" From: \"{}\"" - ).format(remapped, staging_dir)) - + self._remap_staging_dir(repre_data, anatomy) representations.append(repre_data) instance.data["representations"] = representations @@ -117,14 +123,21 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): )) anatomy = context.data["anatomy"] - session_set = False + session_is_set = False for path in paths: path = path.format(**{"root": anatomy.roots}) data = self._load_json(path) - if not session_set: - self.log.info("Setting session using data from file") - api.Session.update(data.get("session")) - os.environ.update(data.get("session")) - session_set = True assert data, "failed to load json file" + if not session_is_set: + session_data = data["session"] + remapped = anatomy.roots_obj.path_remapper( + session_data["AVALON_WORKDIR"] + ) + if remapped: + session_data["AVALON_WORKDIR"] = remapped + + self.log.info("Setting session using data from file") + api.Session.update(session_data) + os.environ.update(session_data) + session_is_set = True self._process_path(data, anatomy) From 8947bb5153fef3f0d6f051454b962e85a91cdc87 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 May 2020 14:47:19 +0200 Subject: [PATCH 58/78] colect rendered files use fill_root method from anatomy --- pype/plugins/global/publish/collect_rendered_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index fab43b116a..2ae3f0e3a3 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -125,7 +125,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): anatomy = context.data["anatomy"] session_is_set = False for path in paths: - path = path.format(**{"root": anatomy.roots}) + path = anatomy.fill_root(path) data = self._load_json(path) assert data, "failed to load json file" if not session_is_set: From 1afabb00e2e8b649c2c3b7c7eefc813b56983162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 7 May 2020 15:04:37 +0200 Subject: [PATCH 59/78] fixed path usage, renderer handling and few code style issues --- .../global/publish/submit_publish_job.py | 155 +++++++++++++----- .../maya/publish/submit_maya_deadline.py | 93 ++++------- 2 files changed, 152 insertions(+), 96 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 6d04c8cb01..8688d161e2 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +"""Submit publishing job to farm.""" + import os import json import re @@ -10,7 +13,7 @@ import pyblish.api def _get_script(): - """Get path to the image sequence script""" + """Get path to the image sequence script.""" try: from pype.scripts import publish_filesequence except Exception: @@ -23,8 +26,8 @@ def _get_script(): return os.path.normpath(module_path) -# Logic to retrieve latest files concerning extendFrames def get_latest_version(asset_name, subset_name, family): + """Retrieve latest files concerning extendFrame feature.""" # Get asset asset_name = io.find_one( {"type": "asset", "name": asset_name}, projection={"name": True} @@ -58,9 +61,7 @@ def get_latest_version(asset_name, subset_name, family): def get_resources(version, extension=None): - """ - Get the files from the specific version - """ + """Get the files from the specific version.""" query = {"type": "representation", "parent": version["_id"]} if extension: query["name"] = extension @@ -80,14 +81,25 @@ def get_resources(version, extension=None): return resources -def get_resource_files(resources, frame_range, override=True): +def get_resource_files(resources, frame_range=None): + """Get resource files at given path. + If `frame_range` is specified those outside will be removed. + + Arguments: + resources (list): List of resources + frame_range (list): Frame range to apply override + + Returns: + list of str: list of collected resources + + """ res_collections, _ = clique.assemble(resources) assert len(res_collections) == 1, "Multiple collections found" res_collection = res_collections[0] # Remove any frames - if override: + if frame_range is not None: for frame in frame_range: if frame not in res_collection.indexes: continue @@ -147,7 +159,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "FTRACK_SERVER", "PYPE_SETUP_PATH", "PYPE_METADATA_FILE", - "AVALON_PROJECT" + "AVALON_PROJECT", + "PYPE_LOG_NO_COLORS" ] # pool used to do the publishing job @@ -169,10 +182,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): families_transfer = ["render3d", "render2d", "ftrack", "slate"] def _submit_deadline_post_job(self, instance, job): - """ + """Submit publish job to Deadline. + Deadline specific code separated from :meth:`process` for sake of more universal code. Muster post job is sent directly by Muster submitter, so this type of code isn't necessary for it. + """ data = instance.data.copy() subset = data["subset"] @@ -225,6 +240,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): environment = job["Props"].get("Env", {}) environment["PYPE_METADATA_FILE"] = metadata_path environment["AVALON_PROJECT"] = io.Session["AVALON_PROJECT"] + environment["PYPE_LOG_NO_COLORS"] = "1" i = 0 for index, key in enumerate(environment): if key.upper() in self.enviro_filter: @@ -250,16 +266,20 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): raise Exception(response.text) def _copy_extend_frames(self, instance, representation): - """ + """Copy existing frames from latest version. + This will copy all existing frames from subset's latest version back to render directory and rename them to what renderer is expecting. - :param instance: instance to get required data from - :type instance: pyblish.plugin.Instance - """ + Arguments: + instance (pyblish.plugin.Instance): instance to get required + data from + representation (dict): presentation to operate on + """ import speedcopy + anatomy = instance.context.data["anatomy"] self.log.info("Preparing to copy ...") start = instance.data.get("startFrame") end = instance.data.get("endFrame") @@ -297,9 +317,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # type assert fn is not None, "padding string wasn't found" # list of tuples (source, destination) + staging = representation.get("stagingDir") + staging = anatomy.fill_roots(staging) resource_files.append( (frame, - os.path.join(representation.get("stagingDir"), + os.path.join(staging, "{}{}{}".format(pre, fn.group("frame"), post))) @@ -319,19 +341,20 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "Finished copying %i files" % len(resource_files)) def _create_instances_for_aov(self, instance_data, exp_files): - """ + """Create instance for each AOV found. + This will create new instance for every aov it can detect in expected files list. - :param instance_data: skeleton data for instance (those needed) later - by collector - :type instance_data: pyblish.plugin.Instance - :param exp_files: list of expected files divided by aovs - :type exp_files: list - :returns: list of instances - :rtype: list(publish.plugin.Instance) - """ + Arguments: + instance_data (pyblish.plugin.Instance): skeleton data for instance + (those needed) later by collector + exp_files (list): list of expected files divided by aovs + Returns: + list of instances + + """ task = os.environ["AVALON_TASK"] subset = instance_data["subset"] instances = [] @@ -354,7 +377,19 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): subset_name = '{}_{}'.format(group_name, aov) + anatomy = instance_data.context.data["anatomy"] + staging = os.path.dirname(list(cols[0])[0]) + success, rootless_staging_dir = ( + anatomy.roots_obj.find_root_template_from_path(staging) + ) + if success: + staging = rootless_staging_dir + else: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(staging)) self.log.info("Creating data for: {}".format(subset_name)) @@ -398,22 +433,25 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): return instances def _get_representations(self, instance, exp_files): - """ + """Create representations for file sequences. + This will return representations of expected files if they are not in hierarchy of aovs. There should be only one sequence of files for most cases, but if not - we create representation from each of them. - :param instance: instance for which we are setting representations - :type instance: pyblish.plugin.Instance - :param exp_files: list of expected files - :type exp_files: list - :returns: list of representations - :rtype: list(dict) - """ + Arguments: + instance (pyblish.plugin.Instance): instance for which we are + setting representations + exp_files (list): list of expected files + Returns: + list of representations + + """ representations = [] collections, remainders = clique.assemble(exp_files) bake_render_path = instance.get("bakeRenderPath") + anatomy = instance.context.data["anatomy"] # create representation for every collected sequence for collection in collections: @@ -435,6 +473,18 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): if bake_render_path: preview = False + staging = os.path.dirname(list(collection)[0]) + success, rootless_staging_dir = ( + anatomy.roots_obj.find_root_template_from_path(staging) + ) + if success: + staging = rootless_staging_dir + else: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(staging)) + rep = { "name": ext, "ext": ext, @@ -442,7 +492,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "frameStart": int(instance.get("frameStartHandle")), "frameEnd": int(instance.get("frameEndHandle")), # If expectedFile are absolute, we need only filenames - "stagingDir": os.path.dirname(list(collection)[0]), + "stagingDir": staging, "anatomy_template": "render", "fps": instance.get("fps"), "tags": ["review", "preview"] if preview else [], @@ -458,6 +508,19 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # add reminders as representations for remainder in remainders: ext = remainder.split(".")[-1] + + staging = os.path.dirname(remainder) + success, rootless_staging_dir = ( + anatomy.roots_obj.find_root_template_from_path(staging) + ) + if success: + staging = rootless_staging_dir + else: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(staging)) + rep = { "name": ext, "ext": ext, @@ -490,7 +553,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): instance["families"] = families def process(self, instance): - """ + """Process plugin. + Detect type of renderfarm submission and create and post dependend job in case of Deadline. It creates json file with metadata needed for publishing in directory of render. @@ -631,6 +695,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ) if success: repre["stagingDir"] = rootless_staging_dir + else: + self.log.warning(( + "Could not find root path for remapping \"{}\"." + " This may cause issues on farm." + ).format(staging_dir)) + repre["stagingDir"] = staging_dir if "publish_on_farm" in repre.get("tags"): # create representations attribute of not there @@ -774,12 +844,21 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): with open(metadata_path, "w") as f: json.dump(publish_job, f, indent=4, sort_keys=True) - def _extend_frames(self, asset, subset, start, end, override): - """ - This will get latest version of asset and update frame range based - on minimum and maximuma values - """ + def _extend_frames(self, asset, subset, start, end): + """Get latest version of asset nad update frame range. + Based on minimum and maximuma values. + + Arguments: + asset (str): asset name + subset (str): subset name + start (int): start frame + end (int): end frame + + Returns: + (int, int): upddate frame start/end + + """ # Frame comparison prev_start = None prev_end = None diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 7547f34ba1..239bad8f83 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -1,6 +1,17 @@ +# -*- coding: utf-8 -*- +"""Submitting render job to Deadline. + +This module is taking care of submitting job from Maya to Deadline. It +creates job and set correct environments. Its behavior is controlled by +`DEADLINE_REST_URL` environment variable - pointing to Deadline Web Service +and `MayaSubmitDeadline.use_published (bool)` property telling Deadline to +use published scene workfile or not. +""" + import os import json import getpass +import re import clique from maya import cmds @@ -14,7 +25,7 @@ import pype.maya.lib as lib def get_renderer_variables(renderlayer=None): - """Retrieve the extension which has been set in the VRay settings + """Retrieve the extension which has been set in the VRay settings. Will return None if the current renderer is not VRay For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which @@ -25,8 +36,8 @@ def get_renderer_variables(renderlayer=None): Returns: dict - """ + """ renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) @@ -34,7 +45,7 @@ def get_renderer_variables(renderlayer=None): render_attrs["padding"])) filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] - + prefix_attr = "defaultRenderGlobals.imageFilePrefix" if renderer == "vray": # Maya's renderSettings function does not return V-Ray file extension # so we get the extension from vraySettings @@ -46,62 +57,33 @@ def get_renderer_variables(renderlayer=None): if extension is None: extension = "png" - filename_prefix = "/_/" + if extension == "exr (multichannel)" or extension == "exr (deep)": + extension = "exr" + + prefix_attr = "vraySettings.fileNamePrefix" + elif renderer == "renderman": + prefix_attr = "rmanGlobals.imageFileFormat" + elif renderer == "redshift": + # mapping redshift extension dropdown values to strings + ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] + extension = ext_mapping[ + cmds.getAttr("redshiftOptions.imageFormat") + ] else: # Get the extension, getAttr defaultRenderGlobals.imageFormat # returns an index number. filename_base = os.path.basename(filename_0) extension = os.path.splitext(filename_base)[-1].strip(".") - filename_prefix = cmds.getAttr("defaultRenderGlobals.imageFilePrefix") + filename_prefix = cmds.getAttr(prefix_attr) return {"ext": extension, "filename_prefix": filename_prefix, "padding": padding, "filename_0": filename_0} -def preview_fname(folder, scene, layer, padding, ext): - """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: - folder (str): The root output folder (image path) - scene (str): The scene name - layer (str): The layer name to be rendered - padding (int): The padding length - ext(str): The output file extension - - Returns: - str - - """ - - fileprefix = cmds.getAttr("defaultRenderGlobals.imageFilePrefix") - output = fileprefix + ".{number}.{ext}" - # RenderPass is currently hardcoded to "beauty" because its not important - # for the deadline submission, but we will need something to replace - # "". - mapping = { - "": "{scene}", - "": "{layer}", - "RenderPass": "beauty" - } - for key, value in mapping.items(): - output = output.replace(key, value) - output = output.format( - scene=scene, - layer=layer, - number="#" * padding, - ext=ext - ) - - return os.path.join(folder, output) - - class MayaSubmitDeadline(pyblish.api.InstancePlugin): - """Submit available render layers to Deadline + """Submit available render layers to Deadline. Renders are submitted to a Deadline Web Service as supplied via the environment variable DEADLINE_REST_URL @@ -194,22 +176,17 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): filename = os.path.basename(filepath) comment = context.data.get("comment", "") - scene = os.path.splitext(filename)[0] dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty - renderlayer_name = instance.data['subset'] # beauty - # renderlayer_globals = instance.data["renderGlobals"] - # legacy_layers = renderlayer_globals["UseLegacyRenderLayers"] deadline_user = context.data.get("deadlineUser", getpass.getuser()) jobname = "%s - %s" % (filename, instance.name) # Get the variables depending on the renderer render_variables = get_renderer_variables(renderlayer) - output_filename_0 = preview_fname(folder=dirname, - scene=scene, - layer=renderlayer_name, - padding=render_variables["padding"], - ext=render_variables["ext"]) + output_filename_0 = re.sub( + "(/d+{{{}}})".format(render_variables["padding"]), + "#" * render_variables["padding"], + render_variables["filename_0"]) try: # Ensure render folder exists @@ -284,7 +261,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): for aov, files in exp[0].items(): col = clique.assemble(files)[0][0] outputFile = col.format('{head}{padding}{tail}') - payload['JobInfo']['OutputFilename' + str(expIndex)] = outputFile + payload['JobInfo']['OutputFilename' + str(expIndex)] = outputFile # noqa: E501 OutputFilenames[expIndex] = outputFile expIndex += 1 else: @@ -293,7 +270,6 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload['JobInfo']['OutputFilename' + str(expIndex)] = outputFile # OutputFilenames[expIndex] = outputFile - # We need those to pass them to pype for it to set correct context keys = [ "FTRACK_API_KEY", @@ -334,7 +310,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname(output_filename_0) + instance.data["outputDir"] = os.path.dirname( + render_variables["filename_0"]) instance.data["deadlineSubmissionJob"] = response.json() def preflight_check(self, instance): From 9a7bd4aa608f31b78cc1a0c84970626aebaab331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 7 May 2020 15:22:08 +0200 Subject: [PATCH 60/78] enclose paths in double quotes --- pype/scripts/publish_filesequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/scripts/publish_filesequence.py b/pype/scripts/publish_filesequence.py index 8b99d0560f..905c6b99ba 100644 --- a/pype/scripts/publish_filesequence.py +++ b/pype/scripts/publish_filesequence.py @@ -80,7 +80,7 @@ def __main__(): args = [ os.path.join(pype_root, pype_command), "publish", - " ".join(paths) + " ".join(['"{}"'.format(p) for p in paths]) ] print("Pype command: {}".format(" ".join(args))) From 88124a25891f63c8960f2583ff70b5bf0cb5cc10 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 May 2020 15:50:59 +0200 Subject: [PATCH 61/78] ContextCustomAttributeValue is used for querying hierarchical custom attribute values for better stability --- pype/ftrack/lib/avalon_sync.py | 79 +++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index e915e86184..be9dfd842d 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -692,7 +692,6 @@ class SyncEntitiesFactory: ent_type["name"]: ent_type["id"] for ent_type in ent_types } - attrs = set() # store default values per entity type attrs_per_entity_type = collections.defaultdict(dict) avalon_attrs = collections.defaultdict(dict) @@ -700,9 +699,10 @@ class SyncEntitiesFactory: attrs_per_entity_type_ca_id = collections.defaultdict(dict) avalon_attrs_ca_id = collections.defaultdict(dict) + attribute_key_by_id = {} for cust_attr in custom_attrs: key = cust_attr["key"] - attrs.add(cust_attr["id"]) + attribute_key_by_id[cust_attr["id"]] = key ca_ent_type = cust_attr["entity_type"] if key.startswith("avalon_"): if ca_ent_type == "show": @@ -776,7 +776,7 @@ class SyncEntitiesFactory: "\"{}\"".format(id) for id in sync_ids ]) attributes_joined = ", ".join([ - "\"{}\"".format(name) for name in attrs + "\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys() ]) cust_attr_query = ( @@ -794,13 +794,13 @@ class SyncEntitiesFactory: else: [values] = self.session._call(call_expr) - for value in values["data"]: - entity_id = value["entity_id"] - key = value["configuration"]["key"] + for item in values["data"]: + entity_id = item["entity_id"] + key = attribute_key_by_id[item["configuration_id"]] store_key = "custom_attributes" if key.startswith("avalon_"): store_key = "avalon_attrs" - self.entities_dict[entity_id][store_key][key] = value["value"] + self.entities_dict[entity_id][store_key][key] = item["value"] # process hierarchical attributes self.set_hierarchical_attribute(hier_attrs, sync_ids) @@ -824,6 +824,21 @@ class SyncEntitiesFactory: attr["default"] ) + # Add attribute ids to entities dictionary + avalon_attribute_id_by_key = { + attr_key: attr_id + for attr_id, attr_key in attribute_key_by_id.items() + if attr_key.startswith("avalon_") + } + for entity_id, entity_dict in self.entities_dict.items(): + if "avalon_attrs_id" not in self.entities_dict[entity_id]: + self.entities_dict[entity_id]["avalon_attrs_id"] = {} + + for attr_key, attr_id in avalon_attribute_id_by_key.items(): + self.entities_dict[entity_id]["avalon_attrs_id"][attr_key] = ( + attr_id + ) + # Prepare dict with all hier keys and None values prepare_dict = {} prepare_dict_avalon = {} @@ -845,32 +860,34 @@ class SyncEntitiesFactory: entity_ids_joined = ", ".join([ "\"{}\"".format(id) for id in sync_ids ]) - + attributes_joined = ", ".join([ + "\"{}\"".format(attr_id) for attr_id in attribute_key_by_id.keys() + ]) avalon_hier = [] - for configuration_id in attribute_key_by_id.keys(): - call_expr = [{ - "action": "query", - "expression": ( - "select value, entity_id from CustomAttributeValue " - "where entity_id in ({}) and configuration_id is \"{}\"" - ).format(entity_ids_joined, configuration_id) - }] - if hasattr(self.session, "call"): - [values] = self.session.call(call_expr) - else: - [values] = self.session._call(call_expr) + call_expr = [{ + "action": "query", + "expression": ( + "select value, entity_id from ContextCustomAttributeValue " + "where entity_id in ({}) and configuration_id in ({})" + ).format(entity_ids_joined, attributes_joined) + }] + if hasattr(self.session, "call"): + [values] = self.session.call(call_expr) + else: + [values] = self.session._call(call_expr) - for value in values["data"]: - if value["value"] is None: - continue - entity_id = value["entity_id"] - key = attribute_key_by_id[value["configuration_id"]] - if key.startswith("avalon_"): - store_key = "avalon_attrs" - avalon_hier.append(key) - else: - store_key = "hier_attrs" - self.entities_dict[entity_id][store_key][key] = value["value"] + for item in values["data"]: + value = item["value"] + if value is None: + continue + entity_id = item["entity_id"] + key = attribute_key_by_id[item["configuration_id"]] + if key.startswith("avalon_"): + store_key = "avalon_attrs" + avalon_hier.append(key) + else: + store_key = "hier_attrs" + self.entities_dict[entity_id][store_key][key] = value # Get dictionary with not None hierarchical values to pull to childs top_id = self.ft_project_id From aff69af21bb9eb5803dc5972544ca1430dee3f6c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 May 2020 15:53:27 +0200 Subject: [PATCH 62/78] removed unused variable --- pype/ftrack/lib/avalon_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/lib/avalon_sync.py b/pype/ftrack/lib/avalon_sync.py index be9dfd842d..293b2d0049 100644 --- a/pype/ftrack/lib/avalon_sync.py +++ b/pype/ftrack/lib/avalon_sync.py @@ -830,7 +830,7 @@ class SyncEntitiesFactory: for attr_id, attr_key in attribute_key_by_id.items() if attr_key.startswith("avalon_") } - for entity_id, entity_dict in self.entities_dict.items(): + for entity_id in self.entities_dict.keys(): if "avalon_attrs_id" not in self.entities_dict[entity_id]: self.entities_dict[entity_id]["avalon_attrs_id"] = {} From 5c0c6374627762d858294cb8cad3102e5154365b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 May 2020 16:50:01 +0200 Subject: [PATCH 63/78] collectors order, output path naming --- .../global/publish/collect_anatomy_object.py | 8 +++---- .../global/publish/collect_avalon_entities.py | 2 +- .../global/publish/collect_rendered_files.py | 3 ++- .../global/publish/submit_publish_job.py | 21 +++++++------------ .../maya/publish/submit_maya_deadline.py | 12 ++++++----- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/pype/plugins/global/publish/collect_anatomy_object.py b/pype/plugins/global/publish/collect_anatomy_object.py index d9e6964050..22d924c88b 100644 --- a/pype/plugins/global/publish/collect_anatomy_object.py +++ b/pype/plugins/global/publish/collect_anatomy_object.py @@ -6,7 +6,7 @@ Requires: Provides: context -> anatomy (pypeapp.Anatomy) """ - +import os from avalon import io from pypeapp import Anatomy import pyblish.api @@ -15,12 +15,12 @@ import pyblish.api class CollectAnatomyObject(pyblish.api.ContextPlugin): """Collect Anatomy object into Context""" - order = pyblish.api.CollectorOrder - 0.11 + order = pyblish.api.CollectorOrder - 0.4 label = "Collect Anatomy Object" def process(self, context): - io.install() - project_name = io.Session.get("AVALON_PROJECT") + # io.install() + project_name = os.environ.get("AVALON_PROJECT") if project_name is None: raise AssertionError( "Environment `AVALON_PROJECT` is not set." diff --git a/pype/plugins/global/publish/collect_avalon_entities.py b/pype/plugins/global/publish/collect_avalon_entities.py index 53f11aa693..a92392a2dc 100644 --- a/pype/plugins/global/publish/collect_avalon_entities.py +++ b/pype/plugins/global/publish/collect_avalon_entities.py @@ -15,7 +15,7 @@ import pyblish.api class CollectAvalonEntities(pyblish.api.ContextPlugin): """Collect Anatomy into Context""" - order = pyblish.api.CollectorOrder - 0.02 + order = pyblish.api.CollectorOrder - 0.1 label = "Collect Avalon Entities" def process(self, context): diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 2ae3f0e3a3..93bf8c484f 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -20,13 +20,14 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): `PYPE_PUBLISH_DATA`. Those files _MUST_ share same context. """ - order = pyblish.api.CollectorOrder - 0.1 + order = pyblish.api.CollectorOrder - 0.2 targets = ["filesequence"] label = "Collect rendered frames" _context = None def _load_json(self, path): + path = path.strip('\"') assert os.path.isfile(path), ( "Path to json file doesn't exist. \"{}\"".format(path) ) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 8688d161e2..77b8022000 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -197,9 +197,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): output_dir = instance.data["outputDir"] # Convert output dir to `{root}/rest/of/path/...` with Anatomy - anatomy = instance.context.data["anatomy"] success, rootless_path = ( - anatomy.roots_obj.find_root_template_from_path(output_dir) + self.anatomy.roots_obj.find_root_template_from_path(output_dir) ) if not success: # `rootless_path` is not set to `output_dir` if none of roots match @@ -279,7 +278,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): """ import speedcopy - anatomy = instance.context.data["anatomy"] self.log.info("Preparing to copy ...") start = instance.data.get("startFrame") end = instance.data.get("endFrame") @@ -318,7 +316,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): assert fn is not None, "padding string wasn't found" # list of tuples (source, destination) staging = representation.get("stagingDir") - staging = anatomy.fill_roots(staging) + staging = self.anatomy.fill_roots(staging) resource_files.append( (frame, os.path.join(staging, @@ -377,11 +375,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): subset_name = '{}_{}'.format(group_name, aov) - anatomy = instance_data.context.data["anatomy"] - staging = os.path.dirname(list(cols[0])[0]) success, rootless_staging_dir = ( - anatomy.roots_obj.find_root_template_from_path(staging) + self.anatomy.roots_obj.find_root_template_from_path(staging) ) if success: staging = rootless_staging_dir @@ -451,7 +447,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): representations = [] collections, remainders = clique.assemble(exp_files) bake_render_path = instance.get("bakeRenderPath") - anatomy = instance.context.data["anatomy"] # create representation for every collected sequence for collection in collections: @@ -475,7 +470,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): staging = os.path.dirname(list(collection)[0]) success, rootless_staging_dir = ( - anatomy.roots_obj.find_root_template_from_path(staging) + self.anatomy.roots_obj.find_root_template_from_path(staging) ) if success: staging = rootless_staging_dir @@ -511,7 +506,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): staging = os.path.dirname(remainder) success, rootless_staging_dir = ( - anatomy.roots_obj.find_root_template_from_path(staging) + self.anatomy.roots_obj.find_root_template_from_path(staging) ) if success: staging = rootless_staging_dir @@ -565,6 +560,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): data = instance.data.copy() context = instance.context self.context = context + self.anatomy = instance.context.data["anatomy"] if hasattr(instance, "_log"): data['_log'] = instance._log @@ -624,9 +620,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): except KeyError: source = context.data["currentFile"] - anatomy = instance.context.data["anatomy"] success, rootless_path = ( - anatomy.roots_obj.find_root_template_from_path(source) + self.anatomy.roots_obj.find_root_template_from_path(source) ) if success: source = rootless_path @@ -691,7 +686,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): staging_dir = repre.get("stagingDir") if staging_dir: success, rootless_staging_dir = ( - anatomy.roots_obj.find_root_template_from_path(staging_dir) + self.anatomy.roots_obj.find_root_template_from_path(staging_dir) ) if success: repre["stagingDir"] = rootless_staging_dir diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 239bad8f83..404ef3de0c 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -44,7 +44,12 @@ def get_renderer_variables(renderlayer=None): padding = cmds.getAttr("{}.{}".format(render_attrs["node"], render_attrs["padding"])) - filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] + filename_0 = cmds.renderSettings( + fullPath=True, + gin="#" * int(padding), + lut=True, + layer=renderlayer or lib.get_current_renderlayer())[0] + filename_0 = filename_0.replace('_', '_beauty') prefix_attr = "defaultRenderGlobals.imageFilePrefix" if renderer == "vray": # Maya's renderSettings function does not return V-Ray file extension @@ -183,10 +188,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Get the variables depending on the renderer render_variables = get_renderer_variables(renderlayer) - output_filename_0 = re.sub( - "(/d+{{{}}})".format(render_variables["padding"]), - "#" * render_variables["padding"], - render_variables["filename_0"]) + output_filename_0 = render_variables["filename_0"] try: # Ensure render folder exists From 379554c66f81aad68a2bfcc94413d0db97d7d4fb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 May 2020 16:57:55 +0200 Subject: [PATCH 64/78] replaced remapping of root with filling root in collect rendered files --- .../global/publish/collect_rendered_files.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 93bf8c484f..4ce2d448b9 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -42,15 +42,10 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): ) return data - def _remap_staging_dir(self, data_object, anatomy): + def _fill_staging_dir(self, data_object, anatomy): staging_dir = data_object.get("stagingDir") if staging_dir: - remapped = anatomy.roots_obj.path_remapper(staging_dir) - if remapped: - data_object["stagingDir"] = remapped - self.log.debug(( - "stagingDir was remapped. To: \"{}\" From: \"{}\"" - ).format(remapped, staging_dir)) + data_object["stagingDir"] = anatomy.fill_root(staging_dir) def _process_path(self, data, anatomy): # validate basic necessary data @@ -94,12 +89,12 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): ) self.log.info("Filling stagignDir...") - self._remap_staging_dir(instance_data, anatomy) + self._fill_staging_dir(instance_data, anatomy) instance.data.update(instance_data) representations = [] for repre_data in instance_data.get("representations") or []: - self._remap_staging_dir(repre_data, anatomy) + self._fill_staging_dir(repre_data, anatomy) representations.append(repre_data) instance.data["representations"] = representations From de7fe115320ae2ed43fe2c334acf824a8613241c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 7 May 2020 16:58:08 +0200 Subject: [PATCH 65/78] cleanup commit --- pype/plugins/global/publish/collect_anatomy_object.py | 2 -- pype/plugins/global/publish/submit_publish_job.py | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/collect_anatomy_object.py b/pype/plugins/global/publish/collect_anatomy_object.py index 22d924c88b..8c01ea5c44 100644 --- a/pype/plugins/global/publish/collect_anatomy_object.py +++ b/pype/plugins/global/publish/collect_anatomy_object.py @@ -7,7 +7,6 @@ Provides: context -> anatomy (pypeapp.Anatomy) """ import os -from avalon import io from pypeapp import Anatomy import pyblish.api @@ -19,7 +18,6 @@ class CollectAnatomyObject(pyblish.api.ContextPlugin): label = "Collect Anatomy Object" def process(self, context): - # io.install() project_name = os.environ.get("AVALON_PROJECT") if project_name is None: raise AssertionError( diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 77b8022000..372373db61 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -686,7 +686,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): staging_dir = repre.get("stagingDir") if staging_dir: success, rootless_staging_dir = ( - self.anatomy.roots_obj.find_root_template_from_path(staging_dir) + self.anatomy.roots_obj.find_root_template_from_path( + staging_dir + ) ) if success: repre["stagingDir"] = rootless_staging_dir From 3b3f0a965c5643940af48b233df1cbde6ca35880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Thu, 7 May 2020 17:03:28 +0200 Subject: [PATCH 66/78] handle published scene names --- pype/plugins/maya/publish/submit_maya_deadline.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 404ef3de0c..7d6437b81d 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -188,7 +188,15 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Get the variables depending on the renderer render_variables = get_renderer_variables(renderlayer) - output_filename_0 = render_variables["filename_0"] + filename_0 = render_variables["filename_0"] + if self.use_published: + new_scene = os.path.splitext(filename)[0] + orig_scene = os.path.splitext( + os.path.basename(context.data["currentFile"]))[0] + filename_0 = render_variables["filename_0"].replace( + orig_scene, new_scene) + + output_filename_0 = filename_0 try: # Ensure render folder exists @@ -312,8 +320,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) # Store output dir for unified publisher (filesequence) - instance.data["outputDir"] = os.path.dirname( - render_variables["filename_0"]) + instance.data["outputDir"] = os.path.dirname(filename_0) instance.data["deadlineSubmissionJob"] = response.json() def preflight_check(self, instance): From 6bb0caa15723d83a5b3308b8c7b8cfdb71537a38 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 7 May 2020 19:13:41 +0200 Subject: [PATCH 67/78] removed unnecessary paths --- .../global/publish/collect_rendered_files.py | 39 +++++++++++-------- .../global/publish/submit_publish_job.py | 3 -- .../nuke/publish/submit_nuke_deadline.py | 2 +- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/pype/plugins/global/publish/collect_rendered_files.py b/pype/plugins/global/publish/collect_rendered_files.py index 4ce2d448b9..5229cd9705 100644 --- a/pype/plugins/global/publish/collect_rendered_files.py +++ b/pype/plugins/global/publish/collect_rendered_files.py @@ -119,21 +119,26 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): )) anatomy = context.data["anatomy"] - session_is_set = False - for path in paths: - path = anatomy.fill_root(path) - data = self._load_json(path) - assert data, "failed to load json file" - if not session_is_set: - session_data = data["session"] - remapped = anatomy.roots_obj.path_remapper( - session_data["AVALON_WORKDIR"] - ) - if remapped: - session_data["AVALON_WORKDIR"] = remapped + self.log.info("anatomy: {}".format(anatomy.roots)) + try: + session_is_set = False + for path in paths: + path = anatomy.fill_root(path) + data = self._load_json(path) + assert data, "failed to load json file" + if not session_is_set: + session_data = data["session"] + remapped = anatomy.roots_obj.path_remapper( + session_data["AVALON_WORKDIR"] + ) + if remapped: + session_data["AVALON_WORKDIR"] = remapped - self.log.info("Setting session using data from file") - api.Session.update(session_data) - os.environ.update(session_data) - session_is_set = True - self._process_path(data, anatomy) + self.log.info("Setting session using data from file") + api.Session.update(session_data) + os.environ.update(session_data) + session_is_set = True + self._process_path(data, anatomy) + except Exception as e: + self.log.error(e, exc_info=True) + raise Exception("Error") from e diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 372373db61..e0bd2c6ec0 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -152,12 +152,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): aov_filter = {"maya": ["beauty"]} enviro_filter = [ - "PATH", - "PYTHONPATH", "FTRACK_API_USER", "FTRACK_API_KEY", "FTRACK_SERVER", - "PYPE_SETUP_PATH", "PYPE_METADATA_FILE", "AVALON_PROJECT", "PYPE_LOG_NO_COLORS" diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 4552d320d6..e41eba3ad7 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -201,7 +201,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): if path.lower().startswith('pype_'): environment[path] = os.environ[path] - environment["PATH"] = os.environ["PATH"] + # environment["PATH"] = os.environ["PATH"] # self.log.debug("enviro: {}".format(environment['PYPE_SCRIPTS'])) clean_environment = {} for key, value in environment.items(): From 284534d02cb378b15bafe8e4e3a8d1263839b2de Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 8 May 2020 17:23:05 +0100 Subject: [PATCH 68/78] Nuke file knob was not updating. The file knob was referencing the existing containers file path and not updating to the new representation path. --- pype/plugins/nuke/load/load_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index 083cc86474..1ee8f0481e 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -237,7 +237,7 @@ class LoadSequence(api.Loader): repr_cont = representation["context"] - file = self.fname + file = api.get_representation_path(representation) if not file: repr_id = representation["_id"] From 55c3be323f7aa106db3ccf60acd6e6eb57b39c2b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 11 May 2020 15:56:48 +0200 Subject: [PATCH 69/78] added docstring to action_create_project_structure --- .../action_create_project_structure.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/pype/ftrack/actions/action_create_project_structure.py b/pype/ftrack/actions/action_create_project_structure.py index d418a2e623..e1c5b6b837 100644 --- a/pype/ftrack/actions/action_create_project_structure.py +++ b/pype/ftrack/actions/action_create_project_structure.py @@ -6,6 +6,47 @@ from pypeapp import config, Anatomy class CreateProjectFolders(BaseAction): + """Action create folder structure and may create hierarchy in Ftrack. + + Creation of folder structure and hierarchy in Ftrack is based on presets. + These presets are located in: + `~/pype-config/presets/tools/project_folder_structure.json` + + Example of content: + ```json + { + "__project_root__": { + "prod" : {}, + "resources" : { + "footage": { + "plates": {}, + "offline": {} + }, + "audio": {}, + "art_dept": {} + }, + "editorial" : {}, + "assets[ftrack.Library]": { + "characters[ftrack]": {}, + "locations[ftrack]": {} + }, + "shots[ftrack.Sequence]": { + "scripts": {}, + "editorial[ftrack.Folder]": {} + } + } + } + ``` + Key "__project_root__" indicates root folder (or entity). Each key in + dictionary represents folder name. Value may contain another dictionary + with subfolders. + + Identifier `[ftrack]` in name says that this should be also created in + Ftrack hierarchy. It is possible to specify entity type of item with "." . + If key is `assets[ftrack.Library]` then in ftrack will be created entity + with name "assets" and entity type "Library". It is expected Library entity + type exist in Ftrack. + """ identifier = "create.project.structure" label = "Create Project Structure" From d9138da2b3d91eb9b98edf78544f7fd1e5e2e205 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 11 May 2020 15:11:21 +0100 Subject: [PATCH 70/78] AOV toggling was not collected correctly. Attribute overrides for AOV only works for the legacy render layers, because the render setup do not connect to the attributes. --- pype/plugins/maya/publish/collect_render.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 456b584191..dbc0594c7c 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -122,6 +122,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): workspace = context.data["workspaceDir"] self._rs = renderSetup.instance() + current_layer = self._rs.getVisibleRenderLayer() maya_render_layers = {l.name(): l for l in self._rs.getRenderLayers()} self.maya_layers = maya_render_layers @@ -306,6 +307,10 @@ class CollectMayaRender(pyblish.api.ContextPlugin): instance.data.update(data) self.log.debug("data: {}".format(json.dumps(data, indent=4))) + # Restore current layer. + self.log.info("Restoring to {}".format(current_layer.name())) + self._rs.switchToLayer(current_layer) + def parse_options(self, render_globals): """Get all overrides with a value, skip those without @@ -400,6 +405,8 @@ class ExpectedFiles: multipart = False def get(self, renderer, layer): + renderSetup.instance().switchToLayerUsingLegacyName(layer) + if renderer.lower() == "arnold": return self._get_files(ExpectedFilesArnold(layer)) elif renderer.lower() == "vray": From 6b47ec9b2c00eb45d38c489930a15f9060872206 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 11 May 2020 16:37:00 +0200 Subject: [PATCH 71/78] Removed publish method from avalon_apps rest api --- pype/avalon_apps/rest_api.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pype/avalon_apps/rest_api.py b/pype/avalon_apps/rest_api.py index 1267ee3992..a552d70907 100644 --- a/pype/avalon_apps/rest_api.py +++ b/pype/avalon_apps/rest_api.py @@ -70,24 +70,6 @@ class AvalonRestApi(RestApi): _asset, identificator, _project_name )) - @RestApi.route("/publish/", - url_prefix="/premiere", methods="GET") - def publish(self, request): - """ - http://localhost:8021/premiere/publish/shot021?json_in=this/path/file_in.json&json_out=this/path/file_out.json - """ - asset_name = request.url_data["asset_name"] - query = request.query - data = request.request_data - - output = { - "message": "Got your data. Thanks.", - "your_data": data, - "your_query": query, - "your_asset_is": asset_name - } - return CallbackResult(data=self.result_to_json(output)) - def result_to_json(self, result): """ Converts result of MongoDB query to dict without $oid (ObjectId) keys with help of regex matching. From 165177834d41656ba6436b67d472548cad1dc4fe Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 11 May 2020 16:38:34 +0200 Subject: [PATCH 72/78] rest api handler does not log 4 error records at once --- pype/services/rest_api/lib/handler.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pype/services/rest_api/lib/handler.py b/pype/services/rest_api/lib/handler.py index dc94808237..fc4410a0bc 100644 --- a/pype/services/rest_api/lib/handler.py +++ b/pype/services/rest_api/lib/handler.py @@ -255,10 +255,9 @@ class Handler(http.server.SimpleHTTPRequestHandler): try: in_data = json.loads(in_data_str) except Exception as e: - log.error("Invalid JSON recieved:") - log.error("-" * 80) - log.error(in_data_str) - log.error("-" * 80) + log.error("Invalid JSON recieved: \"{}\"".format( + str(in_data_str) + )) raise Exception("Invalid JSON recieved") from e request_info = RequestInfo( From 5ad17e5ac9384b7ba63e70ad581699938c309e01 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 11 May 2020 16:43:36 +0200 Subject: [PATCH 73/78] fixed store thumbnails action --- pype/ftrack/actions/action_store_thumbnails_to_avalon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/ftrack/actions/action_store_thumbnails_to_avalon.py b/pype/ftrack/actions/action_store_thumbnails_to_avalon.py index 5e601b2150..c95010c5ce 100644 --- a/pype/ftrack/actions/action_store_thumbnails_to_avalon.py +++ b/pype/ftrack/actions/action_store_thumbnails_to_avalon.py @@ -52,7 +52,7 @@ class StoreThumbnailsToAvalon(BaseAction): }) session.commit() - project = get_project_from_entity(entities[0]) + project = self.get_project_from_entity(entities[0]) project_name = project["full_name"] anatomy = Anatomy(project_name) From 4ff38405d142668c282ca2a4bf6528558dcab65b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 11 May 2020 22:10:29 +0100 Subject: [PATCH 74/78] Deadline wait for scene file to sync. --- pype/plugins/fusion/publish/submit_deadline.py | 3 +++ pype/plugins/maya/publish/submit_maya_deadline.py | 3 +++ pype/plugins/nuke/publish/submit_nuke_deadline.py | 3 +++ 3 files changed, 9 insertions(+) diff --git a/pype/plugins/fusion/publish/submit_deadline.py b/pype/plugins/fusion/publish/submit_deadline.py index 6b65f9fe05..e5deb1b070 100644 --- a/pype/plugins/fusion/publish/submit_deadline.py +++ b/pype/plugins/fusion/publish/submit_deadline.py @@ -68,6 +68,9 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): # Top-level group name "BatchName": filename, + # Asset dependency to wait for at least the scene file to sync. + "AssetDependency0": filepath, + # Job name, as seen in Monitor "Name": filename, diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 7547f34ba1..b25c05643c 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -226,6 +226,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # Top-level group name "BatchName": filename, + # Asset dependency to wait for at least the scene file to sync. + "AssetDependency0": filepath, + # Job name, as seen in Monitor "Name": jobname, diff --git a/pype/plugins/nuke/publish/submit_nuke_deadline.py b/pype/plugins/nuke/publish/submit_nuke_deadline.py index 7990c20112..25556fc8bf 100644 --- a/pype/plugins/nuke/publish/submit_nuke_deadline.py +++ b/pype/plugins/nuke/publish/submit_nuke_deadline.py @@ -128,6 +128,9 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Top-level group name "BatchName": script_name, + # Asset dependency to wait for at least the scene file to sync. + "AssetDependency0": script_path, + # Job name, as seen in Monitor "Name": jobname, From 307c12de5b1da1a384681abccbcbcf2504572e36 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 12 May 2020 10:55:55 +0200 Subject: [PATCH 75/78] small fix using string key instead of not existing variable `version` --- pype/plugins/nukestudio/publish/collect_reviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/plugins/nukestudio/publish/collect_reviews.py b/pype/plugins/nukestudio/publish/collect_reviews.py index b91d390e2e..c7fb5222b0 100644 --- a/pype/plugins/nukestudio/publish/collect_reviews.py +++ b/pype/plugins/nukestudio/publish/collect_reviews.py @@ -159,7 +159,7 @@ class CollectReviews(api.InstancePlugin): version_data.update({k: instance.data[k] for k in transfer_data}) if 'version' in instance.data: - version_data["version"] = instance.data[version] + version_data["version"] = instance.data["version"] # add to data of representation version_data.update({ From ea4afbefb99cc4ba19f2c1fe4bae4894786d7266 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 12 May 2020 15:24:03 +0100 Subject: [PATCH 76/78] Check inventory versions on file open. --- pype/nuke/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/nuke/__init__.py b/pype/nuke/__init__.py index e775468996..5ab996b78a 100644 --- a/pype/nuke/__init__.py +++ b/pype/nuke/__init__.py @@ -61,7 +61,6 @@ def reload_config(): reload(module) - def install(): ''' Installing all requarements for Nuke host ''' @@ -72,6 +71,9 @@ def install(): avalon.register_plugin_path(avalon.Creator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) + # Register Avalon event for workfiles loading. + avalon.on("workio.open_file", lib.check_inventory_versions) + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) workfile_settings = lib.WorkfileSettings() # Disable all families except for the ones we explicitly want to see From 777eea927586e7ec5c778a6da1c0dbb38ccc8faa Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 12 May 2020 17:21:42 +0200 Subject: [PATCH 77/78] support darwin for launching apps --- pype/ftrack/lib/ftrack_app_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 21c49e7819..30e7d5866c 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -259,7 +259,7 @@ class AppAction(BaseAction): executable=execfile, args=[], environment=env ) - elif sys.platform.startswith("linux"): + elif sys.platform.startswith("linux") or sys.platform.startswith("darwin"): execfile = os.path.join(path.strip('"'), self.executable) if not os.path.isfile(execfile): msg = "Launcher doesn't exist - {}".format(execfile) @@ -303,7 +303,7 @@ class AppAction(BaseAction): ) } - popen = avalonlib.launch( + popen = avalon.lib.launch( # noqa: F841 "/usr/bin/env", args=["bash", execfile], environment=env ) From 4c5d0fafd78a0c6e481210413f455464bb0c055a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Tue, 12 May 2020 17:48:55 +0200 Subject: [PATCH 78/78] fixed PEP issues --- pype/ftrack/lib/ftrack_app_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/ftrack/lib/ftrack_app_handler.py b/pype/ftrack/lib/ftrack_app_handler.py index 30e7d5866c..f91695edf0 100644 --- a/pype/ftrack/lib/ftrack_app_handler.py +++ b/pype/ftrack/lib/ftrack_app_handler.py @@ -259,7 +259,8 @@ class AppAction(BaseAction): executable=execfile, args=[], environment=env ) - elif sys.platform.startswith("linux") or sys.platform.startswith("darwin"): + elif (sys.platform.startswith("linux") + or sys.platform.startswith("darwin")): execfile = os.path.join(path.strip('"'), self.executable) if not os.path.isfile(execfile): msg = "Launcher doesn't exist - {}".format(execfile)