From 2082e4bb6ed626067665678bda25bb93a5a32774 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 5 Oct 2020 15:46:36 +0200 Subject: [PATCH 01/42] feat(nuke): improving render knobs --- pype/hosts/nuke/lib.py | 8 ++--- .../plugins/nuke/publish/collect_instances.py | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pype/hosts/nuke/lib.py b/pype/hosts/nuke/lib.py index 19a0784327..d896bfe1ef 100644 --- a/pype/hosts/nuke/lib.py +++ b/pype/hosts/nuke/lib.py @@ -431,13 +431,9 @@ def add_rendering_knobs(node): node (obj): with added knobs ''' if "render" not in node.knobs(): - knob = nuke.Boolean_Knob("render", "Render") + knob = nuke.Enumeration_Knob("render", "Render", [ + "Do Not Render", "Locally", "On Farm"]) knob.setFlag(0x1000) - knob.setValue(False) - node.addKnob(knob) - if "render_farm" not in node.knobs(): - knob = nuke.Boolean_Knob("render_farm", "Render on Farm") - knob.setValue(False) node.addKnob(knob) return node diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index 9085e12bd8..f1e7f2bdde 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -76,19 +76,23 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): if node.Class() == "Group": # only alter families for render family if "write" in families_ak: - - if node["render"].value(): - self.log.info("flagged for render") - add_family = "{}.local".format("render") - # dealing with local/farm rendering - if node["render_farm"].value(): - self.log.info("adding render farm family") - add_family = "{}.farm".format("render") - instance.data["transfer"] = False - families.append(add_family) - if "render" in families: - families.remove("render") - family = "write" + target = node["render"].value() + if target == "Do Not Render": + # Local rendering + self.log.info("flagged for no render") + families.append("render") + elif target == "Locally": + # Local rendering + self.log.info("flagged for local render") + families.append("{}.local".format("render")) + elif target == "On Farm": + # Farm rendering + self.log.info("flagged for farm render") + instance.data["transfer"] = False + families.append("{}.farm".format("render")) + if "render" in families: + families.remove("render") + family = "write" node.begin() for i in nuke.allNodes(): From 22869a1507b62c24122e6621b8f195d49fee4132 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 22:51:23 +0200 Subject: [PATCH 02/42] add role_list attribute to BaseHandler --- pype/modules/ftrack/lib/ftrack_base_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/modules/ftrack/lib/ftrack_base_handler.py b/pype/modules/ftrack/lib/ftrack_base_handler.py index d322fbaf23..96a8150399 100644 --- a/pype/modules/ftrack/lib/ftrack_base_handler.py +++ b/pype/modules/ftrack/lib/ftrack_base_handler.py @@ -35,6 +35,7 @@ class BaseHandler(object): type = 'No-type' ignore_me = False preactions = [] + role_list = [] def __init__(self, session, plugins_presets=None): '''Expects a ftrack_api.Session instance''' From 14e192e72c838809b30b5a7920dcd438d7275126 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 22:52:16 +0200 Subject: [PATCH 03/42] extracted rolecheck from _preregister to separate method --- .../modules/ftrack/lib/ftrack_base_handler.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_base_handler.py b/pype/modules/ftrack/lib/ftrack_base_handler.py index 96a8150399..c711896c19 100644 --- a/pype/modules/ftrack/lib/ftrack_base_handler.py +++ b/pype/modules/ftrack/lib/ftrack_base_handler.py @@ -149,20 +149,27 @@ class BaseHandler(object): def reset_session(self): self.session.reset() + def _register_role_check(self): + if not self.role_list or not isinstance(self.role_list, (list, tuple)): + return + + user_entity = self.session.query( + "User where username is \"{}\"".format(self.session.api_user) + ).one() + available = False + lowercase_rolelist = [ + role_name.lower() + for role_name in self.role_list + ] + for role in user_entity["user_security_roles"]: + if role["security_role"]["name"].lower() in lowercase_rolelist: + available = True + break + if available is False: + raise MissingPermision + def _preregister(self): - if hasattr(self, "role_list") and len(self.role_list) > 0: - username = self.session.api_user - user = self.session.query( - 'User where username is "{}"'.format(username) - ).one() - available = False - lowercase_rolelist = [x.lower() for x in self.role_list] - for role in user['user_security_roles']: - if role['security_role']['name'].lower() in lowercase_rolelist: - available = True - break - if available is False: - raise MissingPermision + self._register_role_check() # Custom validations result = self.preregister() From 378022003c9d6465b1704c9bb9f7ffd541f4373b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 22:52:42 +0200 Subject: [PATCH 04/42] modified preregister result check --- pype/modules/ftrack/lib/ftrack_base_handler.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pype/modules/ftrack/lib/ftrack_base_handler.py b/pype/modules/ftrack/lib/ftrack_base_handler.py index c711896c19..e928f2fb88 100644 --- a/pype/modules/ftrack/lib/ftrack_base_handler.py +++ b/pype/modules/ftrack/lib/ftrack_base_handler.py @@ -180,12 +180,11 @@ class BaseHandler(object): ).format(self.__class__.__name__)) return - if result is True: - return - msg = None - if isinstance(result, str): - msg = result - raise PreregisterException(msg) + if result is not True: + msg = None + if isinstance(result, str): + msg = result + raise PreregisterException(msg) def preregister(self): ''' From b96fc36aedc8a239eacbb47ea4d3d92398f0d362 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 23:02:05 +0200 Subject: [PATCH 05/42] implemented ServerAction with modified discovery and register methods --- .../ftrack/lib/ftrack_action_handler.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pype/modules/ftrack/lib/ftrack_action_handler.py b/pype/modules/ftrack/lib/ftrack_action_handler.py index 76c8e41411..a550d9e7d3 100644 --- a/pype/modules/ftrack/lib/ftrack_action_handler.py +++ b/pype/modules/ftrack/lib/ftrack_action_handler.py @@ -195,3 +195,82 @@ class BaseAction(BaseHandler): ).format(str(type(result)))) return result + + +class ServerAction(BaseAction): + """Action class meant to be used on event server. + + Unlike the `BaseAction` roles are not checked on register but on discover. + For the same reason register is modified to not filter topics by username. + """ + + def __init__(self, *args, **kwargs): + if not self.role_list: + self.role_list = set() + else: + self.role_list = set( + role_name.lower() + for role_name in self.role_list + ) + super(ServerAction, self).__init__(*args, **kwargs) + + def _register_role_check(self): + # Skip register role check. + return + + def _discover(self, event): + """Check user discover availability.""" + if not self._check_user_discover(event): + return + return super(ServerAction, self)._discover(event) + + def _check_user_discover(self, event): + """Should be action discovered by user trying to show actions.""" + if not self.role_list: + return True + + user_entity = self._get_user_entity(event) + if not user_entity: + return False + + for role in user_entity["user_security_roles"]: + lowered_role = role["security_role"]["name"].lower() + if lowered_role in self.role_list: + return True + return False + + def _get_user_entity(self, event): + """Query user entity from event.""" + not_set = object() + + # Check if user is already stored in event data + user_entity = event["data"].get("user_entity", not_set) + if user_entity is not_set: + # Query user entity from event + user_info = event.get("source", {}).get("user", {}) + user_id = user_info.get("id") + username = user_info.get("username") + if user_id: + user_entity = self.session.query( + "User where id is {}".format(user_id) + ).first() + if not user_entity and username: + user_entity = self.session.query( + "User where username is {}".format(username) + ).first() + event["data"]["user_entity"] = user_entity + + return user_entity + + def register(self): + """Register subcription to Ftrack event hub.""" + self.session.event_hub.subscribe( + "topic=ftrack.action.discover", + self._discover, + priority=self.priority + ) + + launch_subscription = ( + "topic=ftrack.action.launch and data.actionIdentifier={0}" + ).format(self.identifier) + self.session.event_hub.subscribe(launch_subscription, self._launch) From ef5b917ff32d546186d2e35da6c3fa859d9ce876 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 23:02:23 +0200 Subject: [PATCH 06/42] extracted ServerAction to ftrack.lib --- pype/modules/ftrack/lib/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/lib/__init__.py b/pype/modules/ftrack/lib/__init__.py index d8e9c7a11c..a52e73d10f 100644 --- a/pype/modules/ftrack/lib/__init__.py +++ b/pype/modules/ftrack/lib/__init__.py @@ -2,7 +2,7 @@ from . import avalon_sync from . import credentials from .ftrack_base_handler import BaseHandler from .ftrack_event_handler import BaseEvent -from .ftrack_action_handler import BaseAction, statics_icon +from .ftrack_action_handler import BaseAction, ServerAction, statics_icon from .ftrack_app_handler import AppAction __all__ = ( @@ -11,6 +11,7 @@ __all__ = ( "BaseHandler", "BaseEvent", "BaseAction", + "ServerAction", "statics_icon", "AppAction" ) From 7bc975c5aa2fbad5248a06dbf7b9b87769b3d575 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 23:05:35 +0200 Subject: [PATCH 07/42] simplified server actions with new ServerAction --- .../action_push_frame_values_to_task.py | 42 ++----------------- .../ftrack/events/action_sync_to_avalon.py | 36 ++-------------- 2 files changed, 6 insertions(+), 72 deletions(-) diff --git a/pype/modules/ftrack/events/action_push_frame_values_to_task.py b/pype/modules/ftrack/events/action_push_frame_values_to_task.py index a55c1e46a6..3a538b57eb 100644 --- a/pype/modules/ftrack/events/action_push_frame_values_to_task.py +++ b/pype/modules/ftrack/events/action_push_frame_values_to_task.py @@ -1,10 +1,10 @@ import json import collections import ftrack_api -from pype.modules.ftrack.lib import BaseAction +from pype.modules.ftrack.lib import ServerAction -class PushFrameValuesToTaskAction(BaseAction): +class PushFrameValuesToTaskAction(ServerAction): """Action for testing purpose or as base for new actions.""" # Ignore event handler by default @@ -34,50 +34,14 @@ class PushFrameValuesToTaskAction(BaseAction): "frameStart": "fstart", "frameEnd": "fend" } - discover_role_list = {"Pypeclub", "Administrator", "Project Manager"} - - def register(self): - modified_role_names = set() - for role_name in self.discover_role_list: - modified_role_names.add(role_name.lower()) - self.discover_role_list = modified_role_names - - self.session.event_hub.subscribe( - "topic=ftrack.action.discover", - self._discover, - priority=self.priority - ) - - launch_subscription = ( - "topic=ftrack.action.launch and data.actionIdentifier={0}" - ).format(self.identifier) - self.session.event_hub.subscribe(launch_subscription, self._launch) + role_list = {"Pypeclub", "Administrator", "Project Manager"} def discover(self, session, entities, event): """ Validation """ # Check if selection is valid - valid_selection = False for ent in event["data"]["selection"]: # Ignore entities that are not tasks or projects if ent["entityType"].lower() == "show": - valid_selection = True - break - - if not valid_selection: - return False - - # Get user and check his roles - user_id = event.get("source", {}).get("user", {}).get("id") - if not user_id: - return False - - user = session.query("User where id is \"{}\"".format(user_id)).first() - if not user: - return False - - for role in user["user_security_roles"]: - lowered_role = role["security_role"]["name"].lower() - if lowered_role in self.discover_role_list: return True return False diff --git a/pype/modules/ftrack/events/action_sync_to_avalon.py b/pype/modules/ftrack/events/action_sync_to_avalon.py index 4e119228c3..7192afeeb6 100644 --- a/pype/modules/ftrack/events/action_sync_to_avalon.py +++ b/pype/modules/ftrack/events/action_sync_to_avalon.py @@ -1,11 +1,11 @@ import time import traceback -from pype.modules.ftrack import BaseAction +from pype.modules.ftrack import ServerAction from pype.modules.ftrack.lib.avalon_sync import SyncEntitiesFactory -class SyncToAvalonServer(BaseAction): +class SyncToAvalonServer(ServerAction): """ Synchronizing data action - from Ftrack to Avalon DB @@ -36,48 +36,18 @@ class SyncToAvalonServer(BaseAction): variant = "- Sync To Avalon (Server)" #: Action description. description = "Send data from Ftrack to Avalon" + role_list = {"Pypeclub", "Administrator", "Project Manager"} def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.entities_factory = SyncEntitiesFactory(self.log, self.session) - def register(self): - self.session.event_hub.subscribe( - "topic=ftrack.action.discover", - self._discover, - priority=self.priority - ) - - launch_subscription = ( - "topic=ftrack.action.launch and data.actionIdentifier={0}" - ).format(self.identifier) - self.session.event_hub.subscribe(launch_subscription, self._launch) - def discover(self, session, entities, event): """ Validation """ # Check if selection is valid - valid_selection = False for ent in event["data"]["selection"]: # Ignore entities that are not tasks or projects if ent["entityType"].lower() in ["show", "task"]: - valid_selection = True - break - - if not valid_selection: - return False - - # Get user and check his roles - user_id = event.get("source", {}).get("user", {}).get("id") - if not user_id: - return False - - user = session.query("User where id is \"{}\"".format(user_id)).first() - if not user: - return False - - role_list = ["Pypeclub", "Administrator", "Project Manager"] - for role in user["user_security_roles"]: - if role["security_role"]["name"] in role_list: return True return False From 2f3d164447f50cc3fd901371eba022b12772c0f9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 5 Oct 2020 23:10:13 +0200 Subject: [PATCH 08/42] extracted ServerAction to ftrack module level --- pype/modules/ftrack/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/__init__.py b/pype/modules/ftrack/__init__.py index fad771f084..a1f0b00ce0 100644 --- a/pype/modules/ftrack/__init__.py +++ b/pype/modules/ftrack/__init__.py @@ -1,6 +1,6 @@ from . import ftrack_server from .ftrack_server import FtrackServer, check_ftrack_url -from .lib import BaseHandler, BaseEvent, BaseAction +from .lib import BaseHandler, BaseEvent, BaseAction, ServerAction __all__ = ( "ftrack_server", @@ -8,5 +8,6 @@ __all__ = ( "check_ftrack_url", "BaseHandler", "BaseEvent", - "BaseAction" + "BaseAction", + "ServerAction" ) From d0b6b6526c32b9b8f3c9afe00f0cb69f221242af Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 6 Oct 2020 12:37:05 +0200 Subject: [PATCH 09/42] make accessing data more efficient --- pype/plugins/harmony/publish/collect_scene.py | 53 ++++++++++++++++++ .../plugins/harmony/publish/extract_render.py | 55 ++++++++----------- .../harmony/publish/extract_template.py | 4 +- .../harmony/publish/extract_workfile.py | 22 +++++--- .../publish/validate_scene_settings.py | 35 +++++++----- 5 files changed, 114 insertions(+), 55 deletions(-) create mode 100644 pype/plugins/harmony/publish/collect_scene.py diff --git a/pype/plugins/harmony/publish/collect_scene.py b/pype/plugins/harmony/publish/collect_scene.py new file mode 100644 index 0000000000..c9a4c31234 --- /dev/null +++ b/pype/plugins/harmony/publish/collect_scene.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +"""Collect scene data.""" +import os + +import pyblish.api +from avalon import harmony + + +class CollectScene(pyblish.api.ContextPlugin): + """Collect basic scene information.""" + + label = "Scene Data" + order = pyblish.api.CollectorOrder + hosts = ["harmony"] + + def process(self, context): + + sig = harmony.signature() + func = """function %s() + { + return [ + about.getApplicationPath(), + scene.currentProjectPath(), + scene.currentScene(), + scene.getFrameRate(), + scene.getStartFrame(), + scene.getStopFrame(), + sound.getSoundtrackAll().path(), + scene.defaultResolutionX(), + scene.defaultResolutionY() + ] + } + %s + """ % (sig, sig) + result = harmony.send( + {"function": func, "args": []} + )["result"] + + context.data["applicationPath"] = result[0] + context.data["scenePath"] = os.path.join( + result[1], result[2] + ".xstage") + context.data["frameRate"] = result[3] + context.data["frameStart"] = result[4] + context.data["frameEnd"] = result[5] + context.data["audioPath"] = result[6] + context.data["resolutionWidth"] = result[7] + context.data["resolutionHeight"] = result[8] + + all_nodes = harmony.send( + {"function": "node.subNodes", "args": ["Top"]} + )["result"] + + context.data["all_nodes"] = all_nodes diff --git a/pype/plugins/harmony/publish/extract_render.py b/pype/plugins/harmony/publish/extract_render.py index 70dceb9ca2..8467f6710e 100644 --- a/pype/plugins/harmony/publish/extract_render.py +++ b/pype/plugins/harmony/publish/extract_render.py @@ -21,42 +21,30 @@ class ExtractRender(pyblish.api.InstancePlugin): def process(self, instance): # Collect scene data. - func = """function func(write_node) - { - return [ - about.getApplicationPath(), - scene.currentProjectPath(), - scene.currentScene(), - scene.getFrameRate(), - scene.getStartFrame(), - scene.getStopFrame(), - sound.getSoundtrackAll().path() - ] - } - func - """ - result = harmony.send( - {"function": func, "args": [instance[0]]} - )["result"] - application_path = result[0] - scene_path = os.path.join(result[1], result[2] + ".xstage") - frame_rate = result[3] - frame_start = result[4] - frame_end = result[5] - audio_path = result[6] - if audio_path: + + application_path = instance.context.data.get("applicationPath") + scene_path = instance.context.data.get("scenePath") + frame_rate = instance.context.data.get("frameRate") + frame_start = instance.context.data.get("frameStart") + frame_end = instance.context.data.get("frameEnd") + audio_path = instance.context.data.get("audioPath") + + if audio_path and os.path.exists(audio_path): + self.log.info(f"Using audio from {audio_path}") instance.data["audio"] = [{"filename": audio_path}] + instance.data["fps"] = frame_rate # Set output path to temp folder. path = tempfile.mkdtemp() - func = """function func(args) + sig = harmony.signature() + func = """function %s(args) { node.setTextAttr(args[0], "DRAWING_NAME", 1, args[1]); } - func - """ - result = harmony.send( + %s + """ % (sig, sig) + harmony.send( { "function": func, "args": [instance[0], path + "/" + instance.data["name"]] @@ -66,6 +54,7 @@ class ExtractRender(pyblish.api.InstancePlugin): # Execute rendering. Ignoring error cause Harmony returns error code # always. + self.log.info(f"running [ {application_path} -batch {scene_path}") proc = subprocess.Popen( [application_path, "-batch", scene_path], stdout=subprocess.PIPE, @@ -73,12 +62,16 @@ class ExtractRender(pyblish.api.InstancePlugin): stdin=subprocess.PIPE ) output, error = proc.communicate() + self.log.info("Click on the line below to see more details.") self.log.info(output.decode("utf-8")) # Collect rendered files. - self.log.debug(path) + self.log.debug(f"collecting from: {path}") files = os.listdir(path) - self.log.debug(files) + assert files, ( + "No rendered files found, render failed." + ) + self.log.debug(f"files there: {files}") collections, remainder = clique.assemble(files, minimum_items=1) assert not remainder, ( "There should not be a remainder for {0}: {1}".format( @@ -89,7 +82,7 @@ class ExtractRender(pyblish.api.InstancePlugin): if len(collections) > 1: for col in collections: if len(list(col)) > 1: - collection = col + collection = col else: collection = collections[0] diff --git a/pype/plugins/harmony/publish/extract_template.py b/pype/plugins/harmony/publish/extract_template.py index 1ba0befc54..9b1b423b88 100644 --- a/pype/plugins/harmony/publish/extract_template.py +++ b/pype/plugins/harmony/publish/extract_template.py @@ -30,9 +30,7 @@ class ExtractTemplate(pype.api.Extractor): unique_backdrops = [backdrops[x] for x in set(backdrops.keys())] # Get non-connected nodes within backdrops. - all_nodes = avalon.harmony.send( - {"function": "node.subNodes", "args": ["Top"]} - )["result"] + all_nodes = instance.context.data.get("all_nodes") for node in [x for x in all_nodes if x not in dependencies]: within_unique_backdrops = bool( [x for x in self.get_backdrops(node) if x in unique_backdrops] diff --git a/pype/plugins/harmony/publish/extract_workfile.py b/pype/plugins/harmony/publish/extract_workfile.py index 304b70e293..1a0183803e 100644 --- a/pype/plugins/harmony/publish/extract_workfile.py +++ b/pype/plugins/harmony/publish/extract_workfile.py @@ -1,8 +1,11 @@ +# -*- coding: utf-8 -*- +"""Extract work file.""" import os import shutil +from zipfile import ZipFile import pype.api -import avalon.harmony +from avalon import harmony import pype.hosts.harmony @@ -14,13 +17,12 @@ class ExtractWorkfile(pype.api.Extractor): families = ["workfile"] def process(self, instance): + """Plugin entry point.""" # Export template. - backdrops = avalon.harmony.send( + backdrops = harmony.send( {"function": "Backdrop.backdrops", "args": ["Top"]} )["result"] - nodes = avalon.harmony.send( - {"function": "node.subNodes", "args": ["Top"]} - )["result"] + nodes = instance.context.data.get("all_nodes") staging_dir = self.staging_dir(instance) filepath = os.path.join(staging_dir, "{}.tpl".format(instance.name)) @@ -29,15 +31,19 @@ class ExtractWorkfile(pype.api.Extractor): # Prep representation. os.chdir(staging_dir) shutil.make_archive( - "{}".format(instance.name), + f"{instance.name}", "zip", - os.path.join(staging_dir, "{}.tpl".format(instance.name)) + os.path.join(staging_dir, f"{instance.name}.tpl") ) + # Check if archive is ok + with ZipFile(os.path.basename("f{instance.name}.zip")) as zr: + if zr.testzip() is not None: + raise Exception("File archive is corrupted.") representation = { "name": "tpl", "ext": "zip", - "files": "{}.zip".format(instance.name), + "files": f"{instance.name}.zip", "stagingDir": staging_dir } instance.data["representations"] = [representation] diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index d7895804bd..34db37de6d 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -1,8 +1,11 @@ +# -*- coding: utf-8 -*- +"""Validate scene settings.""" +import os import json import pyblish.api -import avalon.harmony +from avalon import harmony import pype.hosts.harmony @@ -14,9 +17,17 @@ class ValidateSceneSettingsRepair(pyblish.api.Action): on = "failed" def process(self, context, plugin): + """Repair action entry point.""" pype.hosts.harmony.set_scene_settings( pype.hosts.harmony.get_asset_settings() ) + if not os.patch.exists(context.data["scenePath"]): + self.log.info("correcting scene name") + scene_dir = os.path.dirname(context.data["currentFile"]) + scene_path = os.path.join( + scene_dir, os.path.basename(scene_dir) + ".xstage" + ) + harmony.save_scene_as(scene_path) class ValidateSceneSettings(pyblish.api.InstancePlugin): @@ -31,6 +42,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): frame_check_filter = ["_ch_", "_pr_", "_intd_", "_extd_"] def process(self, instance): + """Plugin entry point.""" expected_settings = pype.hosts.harmony.get_asset_settings() self.log.info(expected_settings) @@ -46,19 +58,13 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): for string in self.frame_check_filter): expected_settings.pop("frameEnd") - func = """function func() - { - return { - "fps": scene.getFrameRate(), - "frameStart": scene.getStartFrame(), - "frameEnd": scene.getStopFrame(), - "resolutionWidth": scene.defaultResolutionX(), - "resolutionHeight": scene.defaultResolutionY() - }; + current_settings = { + "fps": instance.context.data.get("frameRate"), + "frameStart": instance.context.data.get("frameStart"), + "frameEnd": instance.context.data.get("frameEnd"), + "resolutionWidth": instance.context.data.get("resolutionWidth"), + "resolutionHeight": instance.context.data.get("resolutionHeight"), } - func - """ - current_settings = avalon.harmony.send({"function": func})["result"] invalid_settings = [] for key, value in expected_settings.items(): @@ -73,3 +79,6 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): json.dumps(invalid_settings, sort_keys=True, indent=4) ) assert not invalid_settings, msg + assert os.path.exists(instance.context.data.get("scenePath")), ( + "Scene file not found (saved under wrong name)" + ) From ce24026f2b99c599f573f719a4e20916e3bd5913 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 6 Oct 2020 16:10:55 +0200 Subject: [PATCH 10/42] Fix - renaming or deleting tasks in Ftrack wasn't synced Part for manual syncing through Action --- pype/modules/ftrack/lib/avalon_sync.py | 89 +++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 10 deletions(-) diff --git a/pype/modules/ftrack/lib/avalon_sync.py b/pype/modules/ftrack/lib/avalon_sync.py index 28114c7fdc..7ff5283d6a 100644 --- a/pype/modules/ftrack/lib/avalon_sync.py +++ b/pype/modules/ftrack/lib/avalon_sync.py @@ -24,9 +24,9 @@ log = Logger().get_logger(__name__) # Current schemas for avalon types EntitySchemas = { - "project": "avalon-core:project-2.1", - "asset": "avalon-core:asset-3.0", - "config": "avalon-core:config-1.1" + "project": "pype:project-2.1", + "asset": "pype:asset-3.0", + "config": "pype:config-1.1" } # Group name of custom attributes @@ -103,15 +103,40 @@ def get_pype_attr(session, split_hierarchical=True): return custom_attributes -def from_dict_to_set(data): +def from_dict_to_set(data, is_project): """ Converts 'data' into $set part of MongoDB update command. + Sets new or modified keys. + Tasks are updated completely, not per task. (Eg. change in any of the + tasks results in full update of "tasks" from Ftrack. Args: - data: (dictionary) - up-to-date data from Ftrack + data (dictionary): up-to-date data from Ftrack + is_project (boolean): true for project Returns: (dictionary) - { "$set" : "{..}"} """ + not_set = object() + task_changes = not_set + if ( + is_project + and "config" in data + and "tasks" in data["config"] + ): + task_changes = data["config"].pop("tasks") + task_changes_key = "config.tasks" + if not data["config"]: + data.pop("config") + elif ( + not is_project + and "data" in data + and "tasks" in data["data"] + ): + task_changes = data["data"].pop("tasks") + task_changes_key = "data.tasks" + if not data["data"]: + data.pop("data") + result = {"$set": {}} dict_queue = queue.Queue() dict_queue.put((None, data)) @@ -128,6 +153,9 @@ def from_dict_to_set(data): result["$set"][new_key] = value continue dict_queue.put((new_key, value)) + + if task_changes is not not_set and task_changes_key: + result["$set"][task_changes_key] = task_changes return result @@ -659,7 +687,7 @@ class SyncEntitiesFactory: # Tasks must be checked too for task in entity_dict["tasks"].items(): task_name, task = task - passed = task_name + passed = task_names.get(task_name) if passed is None: passed = check_regex( task_name, "task", schema_patterns=_schema_patterns @@ -731,7 +759,7 @@ class SyncEntitiesFactory: for id in ids: if id not in self.entities_dict: continue - self.entities_dict[id]["tasks"].remove(name) + self.entities_dict[id]["tasks"].pop(name) ent_path = self.get_ent_path(id) self.log.warning(failed_regex_msg.format( "/".join([ent_path, name]) @@ -1680,6 +1708,18 @@ class SyncEntitiesFactory: self.updates[avalon_id] ) + # double check changes in tasks, some task could be renamed or + # deleted in Ftrack - not captured otherwise + final_entity = self.entities_dict[ftrack_id]["final_entity"] + if final_entity["data"].get("tasks", {}) != \ + avalon_entity["data"].get("tasks", {}): + if "data" not in self.updates[avalon_id]: + self.updates[avalon_id]["data"] = {} + + self.updates[avalon_id]["data"]["tasks"] = ( + final_entity["data"]["tasks"] + ) + def synchronize(self): self.log.debug("* Synchronization begins") avalon_project_id = self.ftrack_avalon_mapper.get(self.ft_project_id) @@ -2027,15 +2067,20 @@ class SyncEntitiesFactory: self._changeability_by_mongo_id[mongo_id] = is_changeable def update_entities(self): + """ + Runs changes converted to "$set" queries in bulk. + """ mongo_changes_bulk = [] for mongo_id, changes in self.updates.items(): - filter = {"_id": ObjectId(mongo_id)} - change_data = from_dict_to_set(changes) + mongo_id = ObjectId(mongo_id) + is_project = mongo_id == self.avalon_project_id + change_data = from_dict_to_set(changes, is_project) + + filter = {"_id": mongo_id} mongo_changes_bulk.append(UpdateOne(filter, change_data)) if not mongo_changes_bulk: # TODO LOG return - log.debug("mongo_changes_bulk:: {}".format(mongo_changes_bulk)) self.dbcon.bulk_write(mongo_changes_bulk) def reload_parents(self, hierarchy_changing_ids): @@ -2107,6 +2152,18 @@ class SyncEntitiesFactory: ) def compare_dict(self, dict_new, dict_old, _ignore_keys=[]): + """ + Recursively compares and list changes between dictionaries + 'dict_new' and 'dict_old'. + Keys in '_ignore_keys' are skipped and not compared. + Args: + dict_new (dictionary): + dict_old (dictionary): + _ignore_keys (list): + + Returns: + (dictionary) of new or updated keys and theirs values + """ # _ignore_keys may be used for keys nested dict like"data.visualParent" changes = {} ignore_keys = [] @@ -2148,6 +2205,18 @@ class SyncEntitiesFactory: return changes def merge_dicts(self, dict_new, dict_old): + """ + Apply all new or updated keys from 'dict_new' on 'dict_old'. + Recursively. + Doesn't recognise that 'dict_new' doesn't contain some keys + anymore. + Args: + dict_new (dictionary): from Ftrack most likely + dict_old (dictionary): current in DB + + Returns: + (dictionary) of applied changes to original dictionary + """ for key, value in dict_new.items(): if key not in dict_old: dict_old[key] = value From 437147d865f400491e9e11d9d47572b06f4dd660 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 6 Oct 2020 16:27:19 +0200 Subject: [PATCH 11/42] fps and style issues --- pype/plugins/harmony/publish/collect_scene.py | 2 +- pype/plugins/harmony/publish/extract_template.py | 2 +- pype/plugins/harmony/publish/extract_workfile.py | 2 +- pype/plugins/harmony/publish/validate_audio.py | 6 +++++- pype/plugins/harmony/publish/validate_scene_settings.py | 9 ++++++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/pype/plugins/harmony/publish/collect_scene.py b/pype/plugins/harmony/publish/collect_scene.py index c9a4c31234..e24e327afc 100644 --- a/pype/plugins/harmony/publish/collect_scene.py +++ b/pype/plugins/harmony/publish/collect_scene.py @@ -50,4 +50,4 @@ class CollectScene(pyblish.api.ContextPlugin): {"function": "node.subNodes", "args": ["Top"]} )["result"] - context.data["all_nodes"] = all_nodes + context.data["allNodes"] = all_nodes diff --git a/pype/plugins/harmony/publish/extract_template.py b/pype/plugins/harmony/publish/extract_template.py index 9b1b423b88..eaebbbcd37 100644 --- a/pype/plugins/harmony/publish/extract_template.py +++ b/pype/plugins/harmony/publish/extract_template.py @@ -30,7 +30,7 @@ class ExtractTemplate(pype.api.Extractor): unique_backdrops = [backdrops[x] for x in set(backdrops.keys())] # Get non-connected nodes within backdrops. - all_nodes = instance.context.data.get("all_nodes") + all_nodes = instance.context.data.get("allNodes") for node in [x for x in all_nodes if x not in dependencies]: within_unique_backdrops = bool( [x for x in self.get_backdrops(node) if x in unique_backdrops] diff --git a/pype/plugins/harmony/publish/extract_workfile.py b/pype/plugins/harmony/publish/extract_workfile.py index 1a0183803e..41d7868857 100644 --- a/pype/plugins/harmony/publish/extract_workfile.py +++ b/pype/plugins/harmony/publish/extract_workfile.py @@ -22,7 +22,7 @@ class ExtractWorkfile(pype.api.Extractor): backdrops = harmony.send( {"function": "Backdrop.backdrops", "args": ["Top"]} )["result"] - nodes = instance.context.data.get("all_nodes") + nodes = instance.context.data.get("allNodes") staging_dir = self.staging_dir(instance) filepath = os.path.join(staging_dir, "{}.tpl".format(instance.name)) diff --git a/pype/plugins/harmony/publish/validate_audio.py b/pype/plugins/harmony/publish/validate_audio.py index ba113e7610..898ad8ae70 100644 --- a/pype/plugins/harmony/publish/validate_audio.py +++ b/pype/plugins/harmony/publish/validate_audio.py @@ -8,7 +8,11 @@ import pype.hosts.harmony class ValidateAudio(pyblish.api.InstancePlugin): - """Ensures that there is an audio file in the scene. If you are sure that you want to send render without audio, you can disable this validator before clicking on "publish" """ + """Ensures that there is an audio file in the scene. + + If you are sure that you want to send render without audio, you can + disable this validator before clicking on "publish" + """ order = pyblish.api.ValidatorOrder label = "Validate Audio" diff --git a/pype/plugins/harmony/publish/validate_scene_settings.py b/pype/plugins/harmony/publish/validate_scene_settings.py index 34db37de6d..70e6f47721 100644 --- a/pype/plugins/harmony/publish/validate_scene_settings.py +++ b/pype/plugins/harmony/publish/validate_scene_settings.py @@ -58,8 +58,15 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): for string in self.frame_check_filter): expected_settings.pop("frameEnd") + # handle case where ftrack uses only two decimal places + # 23.976023976023978 vs. 23.98 + fps = instance.context.data.get("frameRate") + if isinstance(instance.context.data.get("frameRate"), float): + fps = float( + "{:.2f}".format(instance.context.data.get("frameRate"))) + current_settings = { - "fps": instance.context.data.get("frameRate"), + "fps": fps, "frameStart": instance.context.data.get("frameStart"), "frameEnd": instance.context.data.get("frameEnd"), "resolutionWidth": instance.context.data.get("resolutionWidth"), From c08a6adc570162045ee9008177df4712a57ef5ad Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Oct 2020 15:35:22 +0200 Subject: [PATCH 12/42] Fix - renaming or deleting tasks in Ftrack wasn't synced Part for syncing through Event server --- .../ftrack/events/action_sync_to_avalon.py | 2 +- .../ftrack/events/event_sync_to_avalon.py | 82 ++++++++++++++----- 2 files changed, 63 insertions(+), 21 deletions(-) diff --git a/pype/modules/ftrack/events/action_sync_to_avalon.py b/pype/modules/ftrack/events/action_sync_to_avalon.py index 4e119228c3..01db815413 100644 --- a/pype/modules/ftrack/events/action_sync_to_avalon.py +++ b/pype/modules/ftrack/events/action_sync_to_avalon.py @@ -15,7 +15,7 @@ class SyncToAvalonServer(BaseAction): - Data(dictionary): - VisualParent(ObjectId) - Avalon Id of parent asset - Parents(array of string) - All parent names except project - - Tasks(array of string) - Tasks on asset + - Tasks(dictionary of dictionaries) - Tasks on asset - FtrackId(string) - entityType(string) - entity's type on Ftrack * All Custom attributes in group 'Avalon' diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index d0c9ea2e96..b96741626c 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -472,6 +472,16 @@ class SyncToAvalonEvent(BaseEvent): return filtered_updates def get_ent_path(self, ftrack_id): + """ + Looks for entity in FTrack with 'ftrack_id'. If found returns + concatenated paths from its 'link' elemenent's names. Describes + location of entity in tree. + Args: + ftrack_id (string): entityId of FTrack entity + + Returns: + (string) - example : "/test_project/assets/my_asset" + """ entity = self.ftrack_ents_by_id.get(ftrack_id) if not entity: entity = self.process_session.query( @@ -491,7 +501,7 @@ class SyncToAvalonEvent(BaseEvent): self.process_session.commit() except Exception: self.set_process_session(session) - + self.log.debug("start launch") # Reset object values for each launch self.reset_variables() self._cur_event = event @@ -502,7 +512,7 @@ class SyncToAvalonEvent(BaseEvent): "move": {}, "add": {} } - + self.log.debug("event_data:: {}".format(event["data"])) entities_info = event["data"]["entities"] found_actions = set() for ent_info in entities_info: @@ -741,8 +751,9 @@ class SyncToAvalonEvent(BaseEvent): avalon_ent["data"]["tasks"] ) - if removed_name in self.task_changes_by_avalon_id[mongo_id]: - self.task_changes_by_avalon_id[mongo_id].remove( + if removed_name in self.task_changes_by_avalon_id[mongo_id].\ + keys(): + self.task_changes_by_avalon_id[mongo_id].pop( removed_name ) @@ -1068,11 +1079,13 @@ class SyncToAvalonEvent(BaseEvent): ) # Tasks - tasks = [] + tasks = {} for child in ftrack_ent["children"]: if child.entity_type.lower() != "task": continue - tasks.append(child["name"]) + self.log.debug("child:: {}".format(child)) + task_type = self._get_task_type(child['entityId']) + tasks[child["name"]] = {"type": task_type} # Visual Parent vis_par = None @@ -1423,8 +1436,8 @@ class SyncToAvalonEvent(BaseEvent): avalon_ent["data"]["tasks"] ) - if old_name in self.task_changes_by_avalon_id[mongo_id]: - self.task_changes_by_avalon_id[mongo_id].remove(old_name) + if old_name in self.task_changes_by_avalon_id[mongo_id].keys(): + self.task_changes_by_avalon_id[mongo_id].pop(old_name) else: parent_ftrack_ent = self.ftrack_ents_by_id.get(parent_id) if not parent_ftrack_ent: @@ -1442,17 +1455,21 @@ class SyncToAvalonEvent(BaseEvent): continue child_names.append(child["name"]) - tasks = [task for task in ( - self.task_changes_by_avalon_id[mongo_id] - )] - for task in tasks: - if task not in child_names: - self.task_changes_by_avalon_id[mongo_id].remove( - task + tasks = copy.deepcopy( + self.task_changes_by_avalon_id[mongo_id].keys() + ) + + for task_name in tasks: + if task_name not in child_names: + self.task_changes_by_avalon_id[mongo_id].pop( + task_name ) - if new_name not in self.task_changes_by_avalon_id[mongo_id]: - self.task_changes_by_avalon_id[mongo_id].append(new_name) + task_type = self._get_task_type(ent_info['entityId']) + if new_name not in self.task_changes_by_avalon_id[mongo_id].keys(): + self.task_changes_by_avalon_id[mongo_id][new_name] = { + "type": task_type + } # not_found are not processed since all not found are # not found because they are not synchronizable @@ -1688,8 +1705,12 @@ class SyncToAvalonEvent(BaseEvent): self.regex_failed.append(ent_info["entityId"]) continue - if new_name not in self.task_changes_by_avalon_id[mongo_id]: - self.task_changes_by_avalon_id[mongo_id].append(new_name) + task_type = self._get_task_type(ent_info['entityId']) + if new_name not in \ + self.task_changes_by_avalon_id[mongo_id].keys(): + self.task_changes_by_avalon_id[mongo_id][new_name] = { + "type": task_type + } def _mongo_id_configuration( self, @@ -2293,7 +2314,9 @@ class SyncToAvalonEvent(BaseEvent): mongo_changes_bulk = [] for mongo_id, changes in self.updates.items(): filter = {"_id": mongo_id} - change_data = avalon_sync.from_dict_to_set(changes) + avalon_ent = self.avalon_ents_by_id[mongo_id] + is_project = avalon_ent["type"] == "project" + change_data = avalon_sync.from_dict_to_set(changes, is_project) mongo_changes_bulk.append(UpdateOne(filter, change_data)) if not mongo_changes_bulk: @@ -2477,6 +2500,25 @@ class SyncToAvalonEvent(BaseEvent): ) return True + def _get_task_type(self, entityId): + """ + Returns task type ('Props', 'Art') from Task 'entityId' + Args: + entityId (string): entityId of Task + + Returns: + (string) - None if Task not found + """ + task_type = None + entity = self.process_session.query( + self.entities_query_by_id.format( + self.cur_project["id"], entityId + ) + ).first() + if entity: + task_type = entity["type"]["name"] + return task_type + def register(session, plugins_presets): '''Register plugin. Called when used as an plugin.''' From 3932b19ea94637d2023acde3a039b59db5c4e5a7 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Oct 2020 17:52:36 +0200 Subject: [PATCH 13/42] fix(nuke): improving names of render targets --- pype/hosts/nuke/lib.py | 14 +++++++++----- pype/plugins/nuke/publish/collect_instances.py | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pype/hosts/nuke/lib.py b/pype/hosts/nuke/lib.py index d896bfe1ef..8fd84b8555 100644 --- a/pype/hosts/nuke/lib.py +++ b/pype/hosts/nuke/lib.py @@ -389,24 +389,28 @@ def create_write_node(name, data, input=None, prenodes=None, review=True): # imprinting group node avalon.nuke.imprint(GN, data["avalon"]) - divider = nuke.Text_Knob('') - GN.addKnob(divider) + # add divider + GN.addKnob(nuke.Text_Knob('')) add_rendering_knobs(GN) if review: add_review_knob(GN) + # add divider + GN.addKnob(nuke.Text_Knob('')) + # 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) + link.setFlag(0x1000) GN.addKnob(link) - divider = nuke.Text_Knob('') - GN.addKnob(divider) + # add divider + GN.addKnob(nuke.Text_Knob('')) # adding write to read button add_button_write_to_read(GN) @@ -432,7 +436,7 @@ def add_rendering_knobs(node): ''' if "render" not in node.knobs(): knob = nuke.Enumeration_Knob("render", "Render", [ - "Do Not Render", "Locally", "On Farm"]) + "Use existing frames", "Local", "On farm"]) knob.setFlag(0x1000) node.addKnob(knob) return node diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index f1e7f2bdde..069c8d6c22 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -77,15 +77,15 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): # only alter families for render family if "write" in families_ak: target = node["render"].value() - if target == "Do Not Render": + if target == "Use existing frames": # Local rendering self.log.info("flagged for no render") families.append("render") - elif target == "Locally": + elif target == "Local": # Local rendering self.log.info("flagged for local render") families.append("{}.local".format("render")) - elif target == "On Farm": + elif target == "On farm": # Farm rendering self.log.info("flagged for farm render") instance.data["transfer"] = False From f086088a9e710a3f2b377004a96ecaf20fcdaa3e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Oct 2020 18:03:09 +0200 Subject: [PATCH 14/42] fix(hiero): plate thumbnail correct frame from source range --- pype/plugins/hiero/publish/collect_plates.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/plugins/hiero/publish/collect_plates.py b/pype/plugins/hiero/publish/collect_plates.py index 4d1cc36a92..34ea622414 100644 --- a/pype/plugins/hiero/publish/collect_plates.py +++ b/pype/plugins/hiero/publish/collect_plates.py @@ -192,11 +192,12 @@ class CollectPlatesData(api.InstancePlugin): instance.data["representations"].append( plates_mov_representation) - thumb_frame = instance.data["clipInH"] + ( - (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) + thumb_frame = instance.data["sourceInH"] + ( + (instance.data["sourceOutH"] - instance.data["sourceInH"]) / 2) thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) - + self.log.debug("__ thumb_path: `{}`, frame: `{}`".format( + thumb_path, thumb_frame)) thumbnail = item.thumbnail(thumb_frame).save( thumb_path, format='png' From 6d30b2e852df3da0e3377dcdb2d8f70c6bc81a8c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 7 Oct 2020 18:17:09 +0200 Subject: [PATCH 15/42] fix(hiero): review thumbnail two variation of frame identification --- pype/plugins/hiero/publish/collect_reviews.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pype/plugins/hiero/publish/collect_reviews.py b/pype/plugins/hiero/publish/collect_reviews.py index edb81aed8a..a444d57d6b 100644 --- a/pype/plugins/hiero/publish/collect_reviews.py +++ b/pype/plugins/hiero/publish/collect_reviews.py @@ -142,8 +142,15 @@ class CollectReviews(api.InstancePlugin): staging_dir = os.path.dirname( source_path) - thumb_frame = instance.data["clipInH"] + ( - (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) + media_duration = instance.data.get("mediaDuration") + clip_duration_h = instance.data.get("clipDurationH") + + if media_duration > clip_duration_h: + thumb_frame = instance.data["clipInH"] + ( + (instance.data["clipOutH"] - instance.data["clipInH"]) / 2) + elif media_duration <= clip_duration_h: + thumb_frame = instance.data["sourceIn"] + ( + (instance.data["sourceOut"] - instance.data["sourceIn"]) / 2) thumb_file = "{}_{}{}".format(head, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) self.log.debug("__ thumb_path: {}".format(thumb_path)) From b4a0b1d2589321bc3389e5d849a3fba1cf3e1776 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Oct 2020 18:53:08 +0200 Subject: [PATCH 16/42] change tasks to dictionary --- pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py | 7 +++---- pype/plugins/global/publish/extract_hierarchy_avalon.py | 4 ++-- pype/plugins/hiero/publish/collect_shots.py | 2 +- pype/plugins/hiero/publish/collect_tag_tasks.py | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py index 2ee0898711..e4496138bb 100644 --- a/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py +++ b/pype/plugins/ftrack/publish/integrate_hierarchy_ftrack.py @@ -142,11 +142,10 @@ class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): existing_tasks.append(child['name'].lower()) # existing_tasks.append(child['type']['name']) - for task in tasks: - task_name = next(iter(task)) - task_type = task[task_name]["type"] + for task_name in tasks: + task_type = tasks[task_name]["type"] if task_name.lower() in existing_tasks: - print("Task {} already exists".format(task)) + print("Task {} already exists".format(task_name)) continue tasks_to_create.append((task_name, task_type)) diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 64df672709..72cd935c2d 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -102,11 +102,11 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): new_tasks = data.pop("tasks", {}) if "tasks" not in cur_entity_data and not new_tasks: continue - for task in new_tasks: + for task_name in new_tasks: task_name = next(iter(task)) if task_name in cur_entity_data["tasks"].keys(): continue - cur_entity_data["tasks"][task_name] = task[task_name] + cur_entity_data["tasks"][task_name] = new_tasks[task_name] cur_entity_data.update(data) data = cur_entity_data else: diff --git a/pype/plugins/hiero/publish/collect_shots.py b/pype/plugins/hiero/publish/collect_shots.py index 6f83e08fbe..ed658a19a9 100644 --- a/pype/plugins/hiero/publish/collect_shots.py +++ b/pype/plugins/hiero/publish/collect_shots.py @@ -43,7 +43,7 @@ class CollectShots(api.InstancePlugin): "{} - {} - tasks:{} - assetbuilds:{} - comments:{}".format( data["asset"], data["subset"], - [task.keys()[0] for task in data["tasks"]], + [task for task in data["tasks"]], [x["name"] for x in data.get("assetbuilds", [])], len(data.get("comments", [])) ) diff --git a/pype/plugins/hiero/publish/collect_tag_tasks.py b/pype/plugins/hiero/publish/collect_tag_tasks.py index dbcf5e5260..ae4578bbf7 100644 --- a/pype/plugins/hiero/publish/collect_tag_tasks.py +++ b/pype/plugins/hiero/publish/collect_tag_tasks.py @@ -13,7 +13,7 @@ class CollectClipTagTasks(api.InstancePlugin): # gets tags tags = instance.data["tags"] - tasks = list() + tasks = dict() for t in tags: t_metadata = dict(t["metadata"]) t_family = t_metadata.get("tag.family", "") @@ -22,7 +22,7 @@ class CollectClipTagTasks(api.InstancePlugin): if "task" in t_family: t_task_name = t_metadata.get("tag.label", "") t_task_type = t_metadata.get("tag.type", "") - tasks.append({t_task_name: {"type": t_task_type}}) + tasks[t_task_name] = {"type": t_task_type} instance.data["tasks"] = tasks From d67402331a82f4561fa377cebe4f07d0c67ae64f Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 7 Oct 2020 19:12:32 +0200 Subject: [PATCH 17/42] remove forgotten line --- pype/plugins/global/publish/extract_hierarchy_avalon.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pype/plugins/global/publish/extract_hierarchy_avalon.py b/pype/plugins/global/publish/extract_hierarchy_avalon.py index 72cd935c2d..74751c6807 100644 --- a/pype/plugins/global/publish/extract_hierarchy_avalon.py +++ b/pype/plugins/global/publish/extract_hierarchy_avalon.py @@ -103,7 +103,6 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if "tasks" not in cur_entity_data and not new_tasks: continue for task_name in new_tasks: - task_name = next(iter(task)) if task_name in cur_entity_data["tasks"].keys(): continue cur_entity_data["tasks"][task_name] = new_tasks[task_name] From d6e86f852a09be5630d2933498fbaf676ad42a10 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Oct 2020 20:39:29 +0200 Subject: [PATCH 18/42] Fix - change of task type wasn't propagated Rewrite of approach, simplified, in any task change is noticed, parents of changed tasks are captured, all task entities are pulled from Ftrack and Task dictionaries are recreated from scratch. --- .../ftrack/events/event_sync_to_avalon.py | 473 +++++++++--------- 1 file changed, 249 insertions(+), 224 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index b96741626c..f3ef38b868 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -40,6 +40,12 @@ class SyncToAvalonEvent(BaseEvent): "select id, name, parent_id, link, custom_attributes from TypedContext" " where project_id is \"{}\" and id in ({})" ) + + # useful for getting all tasks for asset + entities_query_by_parent_id = ( + "select id, name, parent_id, link, custom_attributes from TypedContext" + " where project_id is \"{}\" and parent_id in ({})" + ) entities_name_query_by_name = ( "select id, name from TypedContext" " where project_id is \"{}\" and name in ({})" @@ -313,9 +319,6 @@ class SyncToAvalonEvent(BaseEvent): if self._avalon_archived_by_id is not None: self._avalon_archived_by_id[mongo_id] = entity - if mongo_id in self.task_changes_by_avalon_id: - self.task_changes_by_avalon_id.pop(mongo_id) - def _bubble_changeability(self, unchangeable_ids): unchangeable_queue = queue.Queue() for entity_id in unchangeable_ids: @@ -383,8 +386,6 @@ class SyncToAvalonEvent(BaseEvent): self._avalon_archived_by_id = None self._avalon_archived_by_name = None - self.task_changes_by_avalon_id = {} - self._avalon_custom_attributes = None self._ent_types_by_name = None @@ -398,6 +399,10 @@ class SyncToAvalonEvent(BaseEvent): self.ftrack_updated = {} self.ftrack_removed = {} + # set of ftrack ids with modified tasks + # handled separately by full wipeout and replace from FTrack + self.modified_tasks_ftrackids = set() + self.moved_in_avalon = [] self.renamed_in_avalon = [] self.hier_cust_attrs_changes = collections.defaultdict(list) @@ -496,12 +501,24 @@ class SyncToAvalonEvent(BaseEvent): return "/".join([ent["name"] for ent in entity["link"]]) def launch(self, session, event): + """ + Main entry port for synchronization. + Goes through event (can contain multiple changes) and decides if + the event is interesting for us (interest_entTypes). + It separates changes into add|remove|update. + All task changes are handled together by refresh from Ftrack. + Args: + session (object): session to Ftrack + event (dictionary): event content + + Returns: + (boolean or None) + """ # Try to commit and if any error happen then recreate session try: self.process_session.commit() except Exception: self.set_process_session(session) - self.log.debug("start launch") # Reset object values for each launch self.reset_variables() self._cur_event = event @@ -512,7 +529,7 @@ class SyncToAvalonEvent(BaseEvent): "move": {}, "add": {} } - self.log.debug("event_data:: {}".format(event["data"])) + entities_info = event["data"]["entities"] found_actions = set() for ent_info in entities_info: @@ -537,26 +554,45 @@ class SyncToAvalonEvent(BaseEvent): continue ftrack_id = ftrack_id[0] + # task modified, collect parent id of task, handle separately + if entityType.lower() == 'task': + self.modified_tasks_ftrackids.add(ent_info["parentId"]) + if action == "move": ent_keys = ent_info["keys"] - # Seprate update info from move action + # Separate update info from move action if len(ent_keys) > 1: _ent_info = ent_info.copy() for ent_key in ent_keys: if ent_key == "parent_id": + # task parents modified, collect both + if entityType.lower() == 'task': + self.modified_tasks_ftrackids.add( + ent_info["changes"]["new"]) + self.modified_tasks_ftrackids.add( + ent_info["changes"]["old"]) _ent_info["changes"].pop(ent_key, None) _ent_info["keys"].remove(ent_key) else: ent_info["changes"].pop(ent_key, None) ent_info["keys"].remove(ent_key) + if entityType.lower() != 'task': + entities_by_action["update"][ftrack_id] = _ent_info + else: + if entityType.lower() == 'task': + self.modified_tasks_ftrackids.add( + ent_info["changes"]["parent_id"]["new"]) + self.modified_tasks_ftrackids.add( + ent_info["changes"]["parent_id"]["old"] + ) - entities_by_action["update"][ftrack_id] = _ent_info - - found_actions.add(action) - entities_by_action[action][ftrack_id] = ent_info + # regular change process handles all other than Tasks + if entityType.lower() != 'task': + found_actions.add(action) + entities_by_action[action][ftrack_id] = ent_info found_actions = list(found_actions) - if not found_actions: + if not found_actions and not self.modified_tasks_ftrackids: return True # Check if auto sync was turned on/off @@ -632,26 +668,17 @@ class SyncToAvalonEvent(BaseEvent): ft_project["full_name"], debug_msg )) # Get ftrack entities - find all ftrack ids first - ftrack_ids = [] - for ftrack_id in updated: - ftrack_ids.append(ftrack_id) + ftrack_ids = set(updated.keys()) - for action, ftrack_ids in entities_by_action.items(): + for action, _ftrack_ids in entities_by_action.items(): # skip updated (already prepared) and removed (not exist in ftrack) - if action == "remove": - continue + if action not in ("remove", "update"): + ftrack_ids.union(set(_ftrack_ids)) - for ftrack_id in ftrack_ids: - if ftrack_id not in ftrack_ids: - ftrack_ids.append(ftrack_id) - - if ftrack_ids: - joined_ids = ", ".join(["\"{}\"".format(id) for id in ftrack_ids]) - ftrack_entities = self.process_session.query( - self.entities_query_by_id.format(ft_project["id"], joined_ids) - ).all() - for entity in ftrack_entities: - self.ftrack_ents_by_id[entity["id"]] = entity + # collect entity records data which might not be in event + for entity in self._get_entities_for_ftrack_ids(ft_project["id"], + ftrack_ids): + self.ftrack_ents_by_id[entity["id"]] = entity # Filter updates where name is changing for ftrack_id, ent_info in updated.items(): @@ -698,9 +725,11 @@ class SyncToAvalonEvent(BaseEvent): time_6 = time.time() # 6.) Process changes in hierarchy or hier custom attribues self.process_hier_cleanup() + time_7 = time.time() + self.process_task_updates() if self.updates: self.update_entities() - time_7 = time.time() + time_8 = time.time() time_removed = time_2 - time_1 time_renamed = time_3 - time_2 @@ -708,11 +737,15 @@ class SyncToAvalonEvent(BaseEvent): time_moved = time_5 - time_4 time_updated = time_6 - time_5 time_cleanup = time_7 - time_6 - time_total = time_7 - time_1 - self.log.debug("Process time: {} <{}, {}, {}, {}, {}, {}>".format( - time_total, time_removed, time_renamed, time_added, time_moved, - time_updated, time_cleanup - )) + time_task_updates = time_8 - time_7 + time_total = time_8 - time_1 + self.log.debug( + "Process time: {:.2f} <{:.2f}, {:.2f}, {:.2f}, ".format( + time_total, time_removed, time_renamed, time_added) + + "{:.2f}, {:.2f}, {:.2f}, {:.2f}>".format( + time_moved, time_updated, time_cleanup, time_task_updates + ) + ) except Exception: msg = "An error has happened during synchronization" @@ -724,6 +757,9 @@ class SyncToAvalonEvent(BaseEvent): return True def process_removed(self): + """ + Handles removed entities (not removed tasks - handle separately). + """ if not self.ftrack_removed: return ent_infos = self.ftrack_removed @@ -734,31 +770,12 @@ class SyncToAvalonEvent(BaseEvent): recreate_ents = [] removed_names = [] for ftrack_id, removed in ent_infos.items(): - entity_type = removed["entity_type"] - parent_id = removed["parentId"] - removed_name = removed["changes"]["name"]["old"] if entity_type == "Task": - avalon_ent = self.avalon_ents_by_ftrack_id.get(parent_id) - if not avalon_ent: - self.log.debug(( - "Parent entity of task was not found in avalon <{}>" - ).format(self.get_ent_path(parent_id))) - continue - - mongo_id = avalon_ent["_id"] - if mongo_id not in self.task_changes_by_avalon_id: - self.task_changes_by_avalon_id[mongo_id] = ( - avalon_ent["data"]["tasks"] - ) - - if removed_name in self.task_changes_by_avalon_id[mongo_id].\ - keys(): - self.task_changes_by_avalon_id[mongo_id].pop( - removed_name - ) - continue + entity_type = removed["entity_type"] + removed_name = removed["changes"]["name"]["old"] + avalon_ent = self.avalon_ents_by_ftrack_id.get(ftrack_id) if not avalon_ent: continue @@ -1084,7 +1101,8 @@ class SyncToAvalonEvent(BaseEvent): if child.entity_type.lower() != "task": continue self.log.debug("child:: {}".format(child)) - task_type = self._get_task_type(child['entityId']) + task_type = self._get_task_type(self.cur_project["id"], + child['entityId']) tasks[child["name"]] = {"type": task_type} # Visual Parent @@ -1280,21 +1298,14 @@ class SyncToAvalonEvent(BaseEvent): "Processing renamed entities: {}".format(str(ent_infos)) ) - renamed_tasks = {} - not_found = {} changeable_queue = queue.Queue() for ftrack_id, ent_info in ent_infos.items(): entity_type = ent_info["entity_type"] + if entity_type == "Task": + continue + new_name = ent_info["changes"]["name"]["new"] old_name = ent_info["changes"]["name"]["old"] - if entity_type == "Task": - parent_id = ent_info["parentId"] - renamed_tasks[parent_id] = { - "new": new_name, - "old": old_name, - "ent_info": ent_info - } - continue ent_path = self.get_ent_path(ftrack_id) avalon_ent = self.avalon_ents_by_ftrack_id.get(ftrack_id) @@ -1413,64 +1424,6 @@ class SyncToAvalonEvent(BaseEvent): if old_names: self.check_names_synchronizable(old_names) - for parent_id, task_change in renamed_tasks.items(): - avalon_ent = self.avalon_ents_by_ftrack_id.get(parent_id) - ent_info = task_change["ent_info"] - if not avalon_ent: - not_found[ent_info["entityId"]] = ent_info - continue - - new_name = task_change["new"] - old_name = task_change["old"] - passed_regex = avalon_sync.check_regex( - new_name, "task", schema_patterns=self.regex_schemas - ) - if not passed_regex: - ftrack_id = ent_info["enityId"] - self.regex_failed.append(ftrack_id) - continue - - mongo_id = avalon_ent["_id"] - if mongo_id not in self.task_changes_by_avalon_id: - self.task_changes_by_avalon_id[mongo_id] = ( - avalon_ent["data"]["tasks"] - ) - - if old_name in self.task_changes_by_avalon_id[mongo_id].keys(): - self.task_changes_by_avalon_id[mongo_id].pop(old_name) - else: - parent_ftrack_ent = self.ftrack_ents_by_id.get(parent_id) - if not parent_ftrack_ent: - parent_ftrack_ent = self.process_session.query( - self.entities_query_by_id.format( - self.cur_project["id"], parent_id - ) - ).first() - - if parent_ftrack_ent: - self.ftrack_ents_by_id[parent_id] = parent_ftrack_ent - child_names = [] - for child in parent_ftrack_ent["children"]: - if child.entity_type.lower() != "task": - continue - child_names.append(child["name"]) - - tasks = copy.deepcopy( - self.task_changes_by_avalon_id[mongo_id].keys() - ) - - for task_name in tasks: - if task_name not in child_names: - self.task_changes_by_avalon_id[mongo_id].pop( - task_name - ) - - task_type = self._get_task_type(ent_info['entityId']) - if new_name not in self.task_changes_by_avalon_id[mongo_id].keys(): - self.task_changes_by_avalon_id[mongo_id][new_name] = { - "type": task_type - } - # not_found are not processed since all not found are # not found because they are not synchronizable @@ -1488,7 +1441,6 @@ class SyncToAvalonEvent(BaseEvent): # Skip if already exit in avalon db or tasks entities # - happen when was created by any sync event/action pop_out_ents = [] - new_tasks_by_parent = collections.defaultdict(list) for ftrack_id, ent_info in ent_infos.items(): if self.avalon_ents_by_ftrack_id.get(ftrack_id): pop_out_ents.append(ftrack_id) @@ -1501,9 +1453,6 @@ class SyncToAvalonEvent(BaseEvent): entity_type = ent_info["entity_type"] if entity_type == "Task": - parent_id = ent_info["parentId"] - new_tasks_by_parent[parent_id].append(ent_info) - pop_out_ents.append(ftrack_id) continue name = ( @@ -1680,86 +1629,11 @@ class SyncToAvalonEvent(BaseEvent): self.create_entity_in_avalon(entity, parent_avalon) - for parent_id, ent_infos in new_tasks_by_parent.items(): - avalon_ent = self.avalon_ents_by_ftrack_id.get(parent_id) - if not avalon_ent: - # TODO logging - self.log.debug(( - "Skipping synchronization of task" - " because parent was not found in Avalon DB <{}>" - ).format(self.get_ent_path(parent_id))) - continue - - mongo_id = avalon_ent["_id"] - if mongo_id not in self.task_changes_by_avalon_id: - self.task_changes_by_avalon_id[mongo_id] = ( - avalon_ent["data"]["tasks"] - ) - - for ent_info in ent_infos: - new_name = ent_info["changes"]["name"]["new"] - passed_regex = avalon_sync.check_regex( - new_name, "task", schema_patterns=self.regex_schemas - ) - if not passed_regex: - self.regex_failed.append(ent_info["entityId"]) - continue - - task_type = self._get_task_type(ent_info['entityId']) - if new_name not in \ - self.task_changes_by_avalon_id[mongo_id].keys(): - self.task_changes_by_avalon_id[mongo_id][new_name] = { - "type": task_type - } - - def _mongo_id_configuration( - self, - ent_info, - cust_attrs, - hier_attrs, - temp_dict - ): - # Use hierarchical mongo id attribute if possible. - if "_hierarchical" not in temp_dict: - hier_mongo_id_configuration_id = None - for attr in hier_attrs: - if attr["key"] == CUST_ATTR_ID_KEY: - hier_mongo_id_configuration_id = attr["id"] - break - temp_dict["_hierarchical"] = hier_mongo_id_configuration_id - - hier_mongo_id_configuration_id = temp_dict.get("_hierarchical") - if hier_mongo_id_configuration_id is not None: - return hier_mongo_id_configuration_id - - # Legacy part for cases that MongoID attribute is per entity type. - entity_type = ent_info["entity_type"] - mongo_id_configuration_id = temp_dict.get(entity_type) - if mongo_id_configuration_id is not None: - return mongo_id_configuration_id - - for attr in cust_attrs: - key = attr["key"] - if key != CUST_ATTR_ID_KEY: - continue - - if attr["entity_type"] != ent_info["entityType"]: - continue - - if ( - ent_info["entityType"] == "task" and - attr["object_type_id"] != ent_info["objectTypeId"] - ): - continue - - mongo_id_configuration_id = attr["id"] - break - - temp_dict[entity_type] = mongo_id_configuration_id - - return mongo_id_configuration_id - def process_moved(self): + """ + Handles moved entities to different place in hiearchy. + (Not tasks - handled separately.) + """ if not self.ftrack_moved: return @@ -1893,7 +1767,9 @@ class SyncToAvalonEvent(BaseEvent): ) def process_updated(self): - # Only custom attributes changes should get here + """ + Only custom attributes changes should get here + """ if not self.ftrack_updated: return @@ -1991,8 +1867,7 @@ class SyncToAvalonEvent(BaseEvent): if ( not self.moved_in_avalon and not self.renamed_in_avalon and - not self.hier_cust_attrs_changes and - not self.task_changes_by_avalon_id + not self.hier_cust_attrs_changes ): return @@ -2021,14 +1896,6 @@ class SyncToAvalonEvent(BaseEvent): if not all_keys and key not in hier_cust_attrs_keys: hier_cust_attrs_keys.append(key) - # Tasks preparation **** - for mongo_id, tasks in self.task_changes_by_avalon_id.items(): - avalon_ent = self.avalon_ents_by_id[mongo_id] - if "data" not in self.updates[mongo_id]: - self.updates[mongo_id]["data"] = {} - - self.updates[mongo_id]["data"]["tasks"] = tasks - # Parents preparation *** mongo_to_ftrack_parents = {} missing_ftrack_ents = {} @@ -2310,7 +2177,69 @@ class SyncToAvalonEvent(BaseEvent): self.update_entities() + def process_task_updates(self): + """ + Pull task information for selected ftrack ids to replace stored + existing in Avalon. + Solves problem of changing type (even Status in the future) of + task without storing ftrack id for task in the DB. (Which doesn't + bring much advantage currently and it could be troublesome for + all hosts or plugins (for example Nuke) to collect and store. + Returns: + None + """ + self.log.debug( + "Processing task changes for parents: {}".format( + self.modified_tasks_ftrackids + ) + ) + if not self.modified_tasks_ftrackids: + return + entities = self._get_entities_for_ftrack_ids( + self.cur_project["id"], + self.modified_tasks_ftrackids) + + ftrack_mongo_mapping_found = {} + not_found_ids = [] + tasks_per_ftrack_id = {} + + # prepare all tasks per parentId, eg. Avalon asset record + for entity in entities: + ftrack_id = entity["parent_id"] + if ftrack_id not in tasks_per_ftrack_id: + tasks_per_ftrack_id[ftrack_id] = {} + + passed_regex = avalon_sync.check_regex( + entity["name"], "task", + schema_patterns=self.regex_schemas + ) + if not passed_regex: + entity_id = entity["id"] + self.regex_failed.append(entity_id) + continue + + task = {"type": entity["type"]["name"]} + tasks_per_ftrack_id[ftrack_id][entity["name"]] = task + + # find avalon entity by parentId + # should be there as create was run first + for ftrack_id in tasks_per_ftrack_id.keys(): + avalon_entity = self.avalon_ents_by_ftrack_id.get(ftrack_id) + if not avalon_entity: + not_found_ids.append(ftrack_id) + continue + ftrack_mongo_mapping_found[ftrack_id] = avalon_entity["_id"] + + self._update_avalon_tasks(ftrack_mongo_mapping_found, + tasks_per_ftrack_id) + def update_entities(self): + """ + Update Avalon entities by mongo bulk changes. + Expects self.updates which are transfered to $set part of update + command. + Resets self.updates afterwards. + """ mongo_changes_bulk = [] for mongo_id, changes in self.updates.items(): filter = {"_id": mongo_id} @@ -2500,10 +2429,82 @@ class SyncToAvalonEvent(BaseEvent): ) return True - def _get_task_type(self, entityId): + def _update_avalon_tasks(self, ftrack_mongo_mapping_found, + tasks_per_ftrack_id): """ - Returns task type ('Props', 'Art') from Task 'entityId' + Prepare new "tasks" content for existing records in Avalon. Args: + ftrack_mongo_mapping_found (dictionary): ftrack parentId to + Avalon _id mapping + tasks_per_ftrack_id (dictionary): task dictionaries per ftrack + parentId + + Returns: + None + """ + mongo_changes_bulk = [] + for ftrack_id, mongo_id in ftrack_mongo_mapping_found.items(): + filter = {"_id": mongo_id} + change_data = {"$set": {}} + change_data["$set"]["data.tasks"] = tasks_per_ftrack_id[ftrack_id] + mongo_changes_bulk.append(UpdateOne(filter, change_data)) + if not mongo_changes_bulk: + return + + self.dbcon.bulk_write(mongo_changes_bulk) + + def _mongo_id_configuration( + self, + ent_info, + cust_attrs, + hier_attrs, + temp_dict + ): + # Use hierarchical mongo id attribute if possible. + if "_hierarchical" not in temp_dict: + hier_mongo_id_configuration_id = None + for attr in hier_attrs: + if attr["key"] == CUST_ATTR_ID_KEY: + hier_mongo_id_configuration_id = attr["id"] + break + temp_dict["_hierarchical"] = hier_mongo_id_configuration_id + + hier_mongo_id_configuration_id = temp_dict.get("_hierarchical") + if hier_mongo_id_configuration_id is not None: + return hier_mongo_id_configuration_id + + # Legacy part for cases that MongoID attribute is per entity type. + entity_type = ent_info["entity_type"] + mongo_id_configuration_id = temp_dict.get(entity_type) + if mongo_id_configuration_id is not None: + return mongo_id_configuration_id + + for attr in cust_attrs: + key = attr["key"] + if key != CUST_ATTR_ID_KEY: + continue + + if attr["entity_type"] != ent_info["entityType"]: + continue + + if ( + ent_info["entityType"] == "task" and + attr["object_type_id"] != ent_info["objectTypeId"] + ): + continue + + mongo_id_configuration_id = attr["id"] + break + + temp_dict[entity_type] = mongo_id_configuration_id + + return mongo_id_configuration_id + + def _get_task_type(self, project_id, entityId): + """ + Returns task type ('Props', 'Art') from Task 'entityId'. + Args: + project_id (string): entityId (string): entityId of Task Returns: @@ -2512,13 +2513,37 @@ class SyncToAvalonEvent(BaseEvent): task_type = None entity = self.process_session.query( self.entities_query_by_id.format( - self.cur_project["id"], entityId + project_id, entityId ) ).first() if entity: task_type = entity["type"]["name"] return task_type + def _get_entities_for_ftrack_ids(self, ft_project_id, ftrack_ids): + """ + Query Ftrack API and return all entities for particular + 'ft_project' and their parent_id in 'ftrack_ids'. + It is much faster to run this once for multiple ids than run it + for each separately. + Used mainly for collecting task information + Args: + ft_project_id (string): + ftrack_ids (list): of strings + + Returns: + (list) of Ftrack entities + """ + ftrack_entities = [] + if ftrack_ids: + joined_ids = ", ".join(["\"{}\"".format(id) for id in ftrack_ids]) + ftrack_entities = self.process_session.query( + self.entities_query_by_parent_id.format(ft_project_id, + joined_ids) + ).all() + + return ftrack_entities + def register(session, plugins_presets): '''Register plugin. Called when used as an plugin.''' From faa1d8028a5cfbfb73a2178bb7f22e2ab2bb6245 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:32:06 +0200 Subject: [PATCH 19/42] filter tasks by entity_type and do task filtration at one place --- .../ftrack/events/event_sync_to_avalon.py | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index f3ef38b868..d021938606 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -555,8 +555,16 @@ class SyncToAvalonEvent(BaseEvent): ftrack_id = ftrack_id[0] # task modified, collect parent id of task, handle separately - if entityType.lower() == 'task': - self.modified_tasks_ftrackids.add(ent_info["parentId"]) + if entity_type.lower() == "task": + changes = ent_info.get("changes") or {} + if action == "move": + parent_changes = changes["parent_id"] + self.modified_tasks_ftrackids.add(parent_changes["new"]) + self.modified_tasks_ftrackids.add(parent_changes["old"]) + + elif "typeid" in changes or "name" in changes: + self.modified_tasks_ftrackids.add(ent_info["parentId"]) + continue if action == "move": ent_keys = ent_info["keys"] @@ -565,31 +573,15 @@ class SyncToAvalonEvent(BaseEvent): _ent_info = ent_info.copy() for ent_key in ent_keys: if ent_key == "parent_id": - # task parents modified, collect both - if entityType.lower() == 'task': - self.modified_tasks_ftrackids.add( - ent_info["changes"]["new"]) - self.modified_tasks_ftrackids.add( - ent_info["changes"]["old"]) _ent_info["changes"].pop(ent_key, None) _ent_info["keys"].remove(ent_key) else: ent_info["changes"].pop(ent_key, None) ent_info["keys"].remove(ent_key) - if entityType.lower() != 'task': - entities_by_action["update"][ftrack_id] = _ent_info - else: - if entityType.lower() == 'task': - self.modified_tasks_ftrackids.add( - ent_info["changes"]["parent_id"]["new"]) - self.modified_tasks_ftrackids.add( - ent_info["changes"]["parent_id"]["old"] - ) - + entities_by_action["update"][ftrack_id] = _ent_info # regular change process handles all other than Tasks - if entityType.lower() != 'task': - found_actions.add(action) - entities_by_action[action][ftrack_id] = ent_info + found_actions.add(action) + entities_by_action[action][ftrack_id] = ent_info found_actions = list(found_actions) if not found_actions and not self.modified_tasks_ftrackids: From 04c6fd33ebeb80c68a27c9ead8c5d58f7ca26a51 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:32:27 +0200 Subject: [PATCH 20/42] fixed ftrack ids unin --- pype/modules/ftrack/events/event_sync_to_avalon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index d021938606..f7dc34054e 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -665,7 +665,7 @@ class SyncToAvalonEvent(BaseEvent): for action, _ftrack_ids in entities_by_action.items(): # skip updated (already prepared) and removed (not exist in ftrack) if action not in ("remove", "update"): - ftrack_ids.union(set(_ftrack_ids)) + ftrack_ids |= set(_ftrack_ids) # collect entity records data which might not be in event for entity in self._get_entities_for_ftrack_ids(ft_project["id"], From 3d0ab220c5c1fbc0eb740bf28e31e162de814daf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:32:56 +0200 Subject: [PATCH 21/42] do not use `_get_entities_for_ftrack_ids` for query as it find entities by parent id --- pype/modules/ftrack/events/event_sync_to_avalon.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index f7dc34054e..3dc0d92db2 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -668,9 +668,13 @@ class SyncToAvalonEvent(BaseEvent): ftrack_ids |= set(_ftrack_ids) # collect entity records data which might not be in event - for entity in self._get_entities_for_ftrack_ids(ft_project["id"], - ftrack_ids): - self.ftrack_ents_by_id[entity["id"]] = entity + if ftrack_ids: + joined_ids = ", ".join(["\"{}\"".format(id) for id in ftrack_ids]) + ftrack_entities = self.process_session.query( + self.entities_query_by_id.format(ft_project["id"], joined_ids) + ).all() + for entity in ftrack_entities: + self.ftrack_ents_by_id[entity["id"]] = entity # Filter updates where name is changing for ftrack_id, ent_info in updated.items(): From 06cf676bf8373855567f9920272d1253e1514a76 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:33:19 +0200 Subject: [PATCH 22/42] check if there are unprocessed task changes before skipping whole process --- pype/modules/ftrack/events/event_sync_to_avalon.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 3dc0d92db2..dd7ed1386f 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -623,9 +623,10 @@ class SyncToAvalonEvent(BaseEvent): # skip most of events where nothing has changed for avalon if ( - len(found_actions) == 1 and - found_actions[0] == "update" and - not updated + len(found_actions) == 1 + and found_actions[0] == "update" + and not updated + and not self.modified_tasks_ftrackids ): return True From a434041a27de61bbb0e2672d47f7c5958b4efc9f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:33:36 +0200 Subject: [PATCH 23/42] fixed undefined variable in remove --- pype/modules/ftrack/events/event_sync_to_avalon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index dd7ed1386f..f2a94da56c 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -767,10 +767,10 @@ class SyncToAvalonEvent(BaseEvent): recreate_ents = [] removed_names = [] for ftrack_id, removed in ent_infos.items(): - if entity_type == "Task": + entity_type = removed["entity_type"] + if entity_type.lower() == "task": continue - entity_type = removed["entity_type"] removed_name = removed["changes"]["name"]["old"] avalon_ent = self.avalon_ents_by_ftrack_id.get(ftrack_id) From 09e29c3d29787d44a47d0f5e2bd6b10be84bf26f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:34:16 +0200 Subject: [PATCH 24/42] do not process tasks on entity creation but just add it's id to `modified_tasks_ftrackids` so it's processed afterwards --- pype/modules/ftrack/events/event_sync_to_avalon.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index f2a94da56c..9df3c2838f 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -1092,15 +1092,8 @@ class SyncToAvalonEvent(BaseEvent): ) ) - # Tasks - tasks = {} - for child in ftrack_ent["children"]: - if child.entity_type.lower() != "task": - continue - self.log.debug("child:: {}".format(child)) - task_type = self._get_task_type(self.cur_project["id"], - child['entityId']) - tasks[child["name"]] = {"type": task_type} + # Add entity to modified so tasks are added at the end + self.modified_tasks_ftrackids.add(ftrack_ent["id"]) # Visual Parent vis_par = None @@ -1120,7 +1113,7 @@ class SyncToAvalonEvent(BaseEvent): "entityType": ftrack_ent.entity_type, "parents": parents, "hierarchy": hierarchy, - "tasks": tasks, + "tasks": {}, "visualParent": vis_par } } From be4a25dc60177eb552a443e05f70c7ae5b52ecec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:34:41 +0200 Subject: [PATCH 25/42] renamed `entities_query_by_parent_id` to `task_entities_query_by_parent_id` and modified query --- pype/modules/ftrack/events/event_sync_to_avalon.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 9df3c2838f..4bfb648196 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -42,8 +42,8 @@ class SyncToAvalonEvent(BaseEvent): ) # useful for getting all tasks for asset - entities_query_by_parent_id = ( - "select id, name, parent_id, link, custom_attributes from TypedContext" + task_entities_query_by_parent_id = ( + "select id, name, parent_id, type_id from Task" " where project_id is \"{}\" and parent_id in ({})" ) entities_name_query_by_name = ( From bf04ce5fd05d80614e35d916bb10f8ddff960b3f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:35:32 +0200 Subject: [PATCH 26/42] added query for task types to not query them one by one --- pype/modules/ftrack/events/event_sync_to_avalon.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 4bfb648196..93aeb3e20a 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -46,6 +46,9 @@ class SyncToAvalonEvent(BaseEvent): "select id, name, parent_id, type_id from Task" " where project_id is \"{}\" and parent_id in ({})" ) + task_types_query = ( + "select id, name from Type" + ) entities_name_query_by_name = ( "select id, name from TypedContext" " where project_id is \"{}\" and name in ({})" @@ -2192,6 +2195,12 @@ class SyncToAvalonEvent(BaseEvent): ftrack_mongo_mapping_found = {} not_found_ids = [] tasks_per_ftrack_id = {} + # Query all task types at once + task_types = self.process_session.query(self.task_types_query).all() + task_types_by_id = { + task_type["id"]: task_type + for task_type in task_types + } # prepare all tasks per parentId, eg. Avalon asset record for entity in entities: From e2262ce929b049febed5ff67e3c0cdc1952b0382 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:36:04 +0200 Subject: [PATCH 27/42] make sure all entities with changed tasks are processed even if they dont have any task left --- pype/modules/ftrack/events/event_sync_to_avalon.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 93aeb3e20a..5395fe756c 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -2194,7 +2194,12 @@ class SyncToAvalonEvent(BaseEvent): ftrack_mongo_mapping_found = {} not_found_ids = [] - tasks_per_ftrack_id = {} + # Make sure all parents have updated tasks, as they may not have any + tasks_per_ftrack_id = { + ftrack_id: {} + for ftrack_id in self.modified_tasks_ftrackids + } + # Query all task types at once task_types = self.process_session.query(self.task_types_query).all() task_types_by_id = { From 84b1ee867a0ca8f859b63d043325678b3fade3f2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:36:23 +0200 Subject: [PATCH 28/42] used task query for getting task entities --- pype/modules/ftrack/events/event_sync_to_avalon.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 5395fe756c..a77f2c88fa 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -2188,9 +2188,16 @@ class SyncToAvalonEvent(BaseEvent): ) if not self.modified_tasks_ftrackids: return - entities = self._get_entities_for_ftrack_ids( - self.cur_project["id"], - self.modified_tasks_ftrackids) + + joined_ids = ", ".join([ + "\"{}\"".format(ftrack_id) + for ftrack_id in self.modified_tasks_ftrackids + ]) + task_entities = self.process_session.query( + self.task_entities_query_by_parent_id.format( + self.cur_project["id"], joined_ids + ) + ).all() ftrack_mongo_mapping_found = {} not_found_ids = [] From 5a21369cc1de9ecfb68e70e8f93076d719d2bf4a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:36:40 +0200 Subject: [PATCH 29/42] modified for loop in task update to use new changes --- .../ftrack/events/event_sync_to_avalon.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index a77f2c88fa..6baf8e2cd8 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -2215,22 +2215,23 @@ class SyncToAvalonEvent(BaseEvent): } # prepare all tasks per parentId, eg. Avalon asset record - for entity in entities: - ftrack_id = entity["parent_id"] + for task_entity in task_entities: + task_type = task_types_by_id[task_entity["type_id"]] + ftrack_id = task_entity["parent_id"] if ftrack_id not in tasks_per_ftrack_id: tasks_per_ftrack_id[ftrack_id] = {} passed_regex = avalon_sync.check_regex( - entity["name"], "task", - schema_patterns=self.regex_schemas - ) + task_entity["name"], "task", + schema_patterns=self.regex_schemas + ) if not passed_regex: - entity_id = entity["id"] - self.regex_failed.append(entity_id) + self.regex_failed.append(task_entity["id"]) continue - task = {"type": entity["type"]["name"]} - tasks_per_ftrack_id[ftrack_id][entity["name"]] = task + tasks_per_ftrack_id[ftrack_id][task_entity["name"]] = { + "type": task_type["name"] + } # find avalon entity by parentId # should be there as create was run first From 7f4d33ee573a1df48576651037bb3119c75ab8cf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:36:57 +0200 Subject: [PATCH 30/42] removed unused methods --- .../ftrack/events/event_sync_to_avalon.py | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 6baf8e2cd8..0c76cb452e 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -2512,50 +2512,6 @@ class SyncToAvalonEvent(BaseEvent): return mongo_id_configuration_id - def _get_task_type(self, project_id, entityId): - """ - Returns task type ('Props', 'Art') from Task 'entityId'. - Args: - project_id (string): - entityId (string): entityId of Task - - Returns: - (string) - None if Task not found - """ - task_type = None - entity = self.process_session.query( - self.entities_query_by_id.format( - project_id, entityId - ) - ).first() - if entity: - task_type = entity["type"]["name"] - return task_type - - def _get_entities_for_ftrack_ids(self, ft_project_id, ftrack_ids): - """ - Query Ftrack API and return all entities for particular - 'ft_project' and their parent_id in 'ftrack_ids'. - It is much faster to run this once for multiple ids than run it - for each separately. - Used mainly for collecting task information - Args: - ft_project_id (string): - ftrack_ids (list): of strings - - Returns: - (list) of Ftrack entities - """ - ftrack_entities = [] - if ftrack_ids: - joined_ids = ", ".join(["\"{}\"".format(id) for id in ftrack_ids]) - ftrack_entities = self.process_session.query( - self.entities_query_by_parent_id.format(ft_project_id, - joined_ids) - ).all() - - return ftrack_entities - def register(session, plugins_presets): '''Register plugin. Called when used as an plugin.''' From 9225c3553de6c673cf2a5351fd4b9feaccd91a64 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Oct 2020 13:37:05 +0200 Subject: [PATCH 31/42] formatting changes --- .../ftrack/events/event_sync_to_avalon.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pype/modules/ftrack/events/event_sync_to_avalon.py b/pype/modules/ftrack/events/event_sync_to_avalon.py index 0c76cb452e..251d87331e 100644 --- a/pype/modules/ftrack/events/event_sync_to_avalon.py +++ b/pype/modules/ftrack/events/event_sync_to_avalon.py @@ -739,13 +739,13 @@ class SyncToAvalonEvent(BaseEvent): time_cleanup = time_7 - time_6 time_task_updates = time_8 - time_7 time_total = time_8 - time_1 - self.log.debug( - "Process time: {:.2f} <{:.2f}, {:.2f}, {:.2f}, ".format( - time_total, time_removed, time_renamed, time_added) + - "{:.2f}, {:.2f}, {:.2f}, {:.2f}>".format( - time_moved, time_updated, time_cleanup, time_task_updates - ) - ) + self.log.debug(( + "Process time: {:.2f} <{:.2f}, {:.2f}, {:.2f}, " + "{:.2f}, {:.2f}, {:.2f}, {:.2f}>" + ).format( + time_total, time_removed, time_renamed, time_added, + time_moved, time_updated, time_cleanup, time_task_updates + )) except Exception: msg = "An error has happened during synchronization" @@ -2242,8 +2242,10 @@ class SyncToAvalonEvent(BaseEvent): continue ftrack_mongo_mapping_found[ftrack_id] = avalon_entity["_id"] - self._update_avalon_tasks(ftrack_mongo_mapping_found, - tasks_per_ftrack_id) + self._update_avalon_tasks( + ftrack_mongo_mapping_found, + tasks_per_ftrack_id + ) def update_entities(self): """ @@ -2441,8 +2443,9 @@ class SyncToAvalonEvent(BaseEvent): ) return True - def _update_avalon_tasks(self, ftrack_mongo_mapping_found, - tasks_per_ftrack_id): + def _update_avalon_tasks( + self, ftrack_mongo_mapping_found, tasks_per_ftrack_id + ): """ Prepare new "tasks" content for existing records in Avalon. Args: @@ -2460,10 +2463,9 @@ class SyncToAvalonEvent(BaseEvent): change_data = {"$set": {}} change_data["$set"]["data.tasks"] = tasks_per_ftrack_id[ftrack_id] mongo_changes_bulk.append(UpdateOne(filter, change_data)) - if not mongo_changes_bulk: - return - self.dbcon.bulk_write(mongo_changes_bulk) + if mongo_changes_bulk: + self.dbcon.bulk_write(mongo_changes_bulk) def _mongo_id_configuration( self, From 04b35dbea592bdbfad0984ce8248180c983acec8 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 8 Oct 2020 14:04:43 +0100 Subject: [PATCH 32/42] Add source for review instances. --- pype/plugins/ftrack/publish/integrate_ftrack_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py index 2646dc90cc..549dc22d79 100644 --- a/pype/plugins/ftrack/publish/integrate_ftrack_instances.py +++ b/pype/plugins/ftrack/publish/integrate_ftrack_instances.py @@ -154,6 +154,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): # Create copy with ftrack.unmanaged location if thumb or prev if comp.get('thumbnail') or comp.get('preview') \ or ("preview" in comp.get('tags', [])) \ + or ("review" in comp.get('tags', [])) \ or ("thumbnail" in comp.get('tags', [])): unmanaged_loc = self.get_ftrack_location( 'ftrack.unmanaged', ft_session From 3fc15cca6d5cd76e96c9e32a045afd199cdcf8c9 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Oct 2020 15:59:26 +0200 Subject: [PATCH 33/42] fix(hiero): fps order of clip instance data collecting --- pype/plugins/hiero/publish/collect_clips.py | 5 +++-- pype/plugins/hiero/publish/collect_framerate.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pype/plugins/hiero/publish/collect_clips.py b/pype/plugins/hiero/publish/collect_clips.py index e11ad93883..2c7ea3ec60 100644 --- a/pype/plugins/hiero/publish/collect_clips.py +++ b/pype/plugins/hiero/publish/collect_clips.py @@ -144,8 +144,9 @@ class CollectClips(api.ContextPlugin): "family": "clip", "families": [], "handleStart": projectdata.get("handleStart", 0), - "handleEnd": projectdata.get("handleEnd", 0)}) - + "handleEnd": projectdata.get("handleEnd", 0), + "fps": context.data["fps"] + }) instance = context.create_instance(**data) self.log.info("Created instance: {}".format(instance)) diff --git a/pype/plugins/hiero/publish/collect_framerate.py b/pype/plugins/hiero/publish/collect_framerate.py index 6d2d2eef2b..e11433adb1 100644 --- a/pype/plugins/hiero/publish/collect_framerate.py +++ b/pype/plugins/hiero/publish/collect_framerate.py @@ -4,13 +4,14 @@ from pyblish import api class CollectFramerate(api.ContextPlugin): """Collect framerate from selected sequence.""" - order = api.CollectorOrder + 0.01 + order = api.CollectorOrder + 0.001 label = "Collect Framerate" hosts = ["hiero"] def process(self, context): sequence = context.data["activeSequence"] context.data["fps"] = self.get_rate(sequence) + self.log.info("Framerate is collected: {}".format(context.data["fps"])) def get_rate(self, sequence): num, den = sequence.framerate().toRational() From 1b82342906969714cb6de4e2f1ac41e8950e00a1 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Oct 2020 19:40:14 +0200 Subject: [PATCH 34/42] feat(nuke): validate legacy writes --- .../nuke/publish/collect_legacy_write.py | 31 ---- .../nuke/publish/validate_write_legacy.py | 154 ++++++++++-------- 2 files changed, 88 insertions(+), 97 deletions(-) delete mode 100644 pype/plugins/nuke/publish/collect_legacy_write.py diff --git a/pype/plugins/nuke/publish/collect_legacy_write.py b/pype/plugins/nuke/publish/collect_legacy_write.py deleted file mode 100644 index cfb0798434..0000000000 --- a/pype/plugins/nuke/publish/collect_legacy_write.py +++ /dev/null @@ -1,31 +0,0 @@ -import pyblish.api - - -class CollectWriteLegacy(pyblish.api.InstancePlugin): - """Collect legacy write nodes.""" - - order = pyblish.api.CollectorOrder + 0.0101 - label = "Collect Write node Legacy" - hosts = ["nuke", "nukeassist"] - - def process(self, instance): - self.log.info(instance[:]) - node = instance[0] - - if node.Class() not in ["Group", "Write"]: - return - - family_knobs = ["ak:family", "avalon:family"] - test = [k for k in node.knobs().keys() if k in family_knobs] - self.log.info(test) - - if len(test) == 1: - if "render" in node[test[0]].value(): - self.log.info("render") - return - - if "render" in node.knobs(): - instance.data.update( - {"family": "write.legacy", - "families": []} - ) diff --git a/pype/plugins/nuke/publish/validate_write_legacy.py b/pype/plugins/nuke/publish/validate_write_legacy.py index 58beb56eba..72e5265d3f 100644 --- a/pype/plugins/nuke/publish/validate_write_legacy.py +++ b/pype/plugins/nuke/publish/validate_write_legacy.py @@ -6,81 +6,103 @@ import nuke from avalon import api import re import pyblish.api +import pype.api from avalon.nuke import get_avalon_knob_data -class RepairWriteLegacyAction(pyblish.api.Action): - - label = "Repair" - icon = "wrench" - on = "failed" - - def process(self, context, plugin): - - # Get the errored instances - failed = [] - for result in context.data["results"]: - if (result["error"] is not None and result["instance"] is not None - and result["instance"] not in failed): - failed.append(result["instance"]) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(failed, plugin) - - for instance in instances: - if "Write" in instance[0].Class(): - data = toml.loads(instance[0]["avalon"].value()) - else: - data = get_avalon_knob_data(instance[0]) - - self.log.info(data) - - data["xpos"] = instance[0].xpos() - data["ypos"] = instance[0].ypos() - data["input"] = instance[0].input(0) - data["publish"] = instance[0]["publish"].value() - data["render"] = instance[0]["render"].value() - data["render_farm"] = instance[0]["render_farm"].value() - data["review"] = instance[0]["review"].value() - - # nuke.delete(instance[0]) - - task = os.environ["AVALON_TASK"] - sanitized_task = re.sub('[^0-9a-zA-Z]+', '', task) - subset_name = "render{}Main".format( - sanitized_task.capitalize()) - - Create_name = "CreateWriteRender" - - creator_plugin = None - for Creator in api.discover(api.Creator): - if Creator.__name__ != Create_name: - continue - - creator_plugin = Creator - - # return api.create() - creator_plugin(data["subset"], data["asset"]).process() - - node = nuke.toNode(data["subset"]) - node.setXYpos(data["xpos"], data["ypos"]) - node.setInput(0, data["input"]) - node["publish"].setValue(data["publish"]) - node["render"].setValue(data["render"]) - node["render_farm"].setValue(data["render_farm"]) - node["review"].setValue(data["review"]) - class ValidateWriteLegacy(pyblish.api.InstancePlugin): """Validate legacy write nodes.""" order = pyblish.api.ValidatorOrder optional = True - families = ["write.legacy"] - label = "Write Legacy" + families = ["write"] + label = "Validate Write Legacy" hosts = ["nuke"] - actions = [RepairWriteLegacyAction] + actions = [pype.api.RepairAction] def process(self, instance): - + node = instance[0] msg = "Clean up legacy write node \"{}\"".format(instance) - assert False, msg + + if node.Class() not in ["Group", "Write"]: + return + + # test avalon knobs + family_knobs = ["ak:family", "avalon:family"] + family_test = [k for k in node.knobs().keys() if k in family_knobs] + self.log.debug("_ family_test: {}".format(family_test)) + + # test if render in family test knob + # and only one item should be available + if len(family_test) == 1: + assert "render" in node[family_test[0]].value(), msg + else: + assert False, msg + + # test if `file` knob in node, this way old + # non-group-node write could be detected + if "file" in node.knobs(): + assert False, msg + + # check if write node is having old render targeting + if "render_farm" in node.knobs(): + assert False, msg + + @classmethod + def repair(cls, instance): + node = instance[0] + + if "Write" in node.Class(): + data = toml.loads(node["avalon"].value()) + else: + data = get_avalon_knob_data(node) + + # collect reusable data + data["XYpos"] = (node.xpos(), node.ypos()) + data["input"] = node.input(0) + data["publish"] = node["publish"].value() + data["render"] = node["render"].value() + data["render_farm"] = node["render_farm"].value() + data["review"] = node["review"].value() + data["use_limit"] = node["use_limit"].value() + data["first"] = node["first"].value() + data["last"] = node["last"].value() + + family = data["family"] + cls.log.debug("_ orig node family: {}".format(family)) + + # define what family of write node should be recreated + if family == "render": + Create_name = "CreateWriteRender" + elif family == "prerender": + Create_name = "CreateWritePrerender" + + # get appropriate plugin class + creator_plugin = None + for Creator in api.discover(api.Creator): + if Creator.__name__ != Create_name: + continue + + creator_plugin = Creator + + # delete the legaci write node + # nuke.delete(node) + + # create write node with creator + new_node_name = data["subset"] + "_1" + creator_plugin(new_node_name, data["asset"]).process() + + node = nuke.toNode(new_node_name) + node.setXYpos(*data["XYpos"]) + node.setInput(0, data["input"]) + node["publish"].setValue(data["publish"]) + node["review"].setValue(data["review"]) + node["use_limit"].setValue(data["use_limit"]) + node["first"].setValue(data["first"]) + node["last"].setValue(data["last"]) + + # recreate render targets + if data["render"]: + node["render"].setValue("Local") + if data["render_farm"]: + node["render"].setValue("On farm") From a78cb55ae6f2ea1a8e71e7080db1f4a5dc3b727b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 8 Oct 2020 19:44:58 +0200 Subject: [PATCH 35/42] hound(nuke) --- pype/plugins/nuke/publish/validate_write_legacy.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pype/plugins/nuke/publish/validate_write_legacy.py b/pype/plugins/nuke/publish/validate_write_legacy.py index 72e5265d3f..d7739b448b 100644 --- a/pype/plugins/nuke/publish/validate_write_legacy.py +++ b/pype/plugins/nuke/publish/validate_write_legacy.py @@ -34,19 +34,15 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin): # test if render in family test knob # and only one item should be available - if len(family_test) == 1: - assert "render" in node[family_test[0]].value(), msg - else: - assert False, msg + assert len(family_test) != 1, msg + assert "render" in node[family_test[0]].value(), msg # test if `file` knob in node, this way old # non-group-node write could be detected - if "file" in node.knobs(): - assert False, msg + assert "file" in node.knobs(), msg # check if write node is having old render targeting - if "render_farm" in node.knobs(): - assert False, msg + assert "render_farm" in node.knobs(), msg @classmethod def repair(cls, instance): From 1f0fbdd5d6a10c11e6ff5fdf92a447073edbd60a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 9 Oct 2020 09:41:28 +0100 Subject: [PATCH 36/42] Add mp4 support for RV action. --- pype/modules/ftrack/actions/action_rv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/modules/ftrack/actions/action_rv.py b/pype/modules/ftrack/actions/action_rv.py index 528eeeee07..f9aeb87f71 100644 --- a/pype/modules/ftrack/actions/action_rv.py +++ b/pype/modules/ftrack/actions/action_rv.py @@ -46,7 +46,7 @@ class RVAction(BaseAction): return self.allowed_types = self.config_data.get( - 'file_ext', ["img", "mov", "exr"] + 'file_ext', ["img", "mov", "exr", "mp4"] ) def discover(self, session, entities, event): From aa35168771a6823a98d174600c424a9cc2b15f52 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Fri, 9 Oct 2020 11:31:45 +0200 Subject: [PATCH 37/42] make sure we replace original node --- pype/plugins/nuke/publish/validate_write_legacy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nuke/publish/validate_write_legacy.py b/pype/plugins/nuke/publish/validate_write_legacy.py index d7739b448b..46f55e896d 100644 --- a/pype/plugins/nuke/publish/validate_write_legacy.py +++ b/pype/plugins/nuke/publish/validate_write_legacy.py @@ -82,10 +82,10 @@ class ValidateWriteLegacy(pyblish.api.InstancePlugin): creator_plugin = Creator # delete the legaci write node - # nuke.delete(node) + nuke.delete(node) # create write node with creator - new_node_name = data["subset"] + "_1" + new_node_name = data["subset"] creator_plugin(new_node_name, data["asset"]).process() node = nuke.toNode(new_node_name) From 61451d3e6b03fc5b8c3a26cd1c79e71945407bd2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Oct 2020 15:46:34 +0200 Subject: [PATCH 38/42] feat(tvpaint): basic integration --- pype/hooks/tvpaint/prelaunch.py | 110 +++++++++++++++++++++++++++ pype/hosts/tvpaint/__init__.py | 1 + pype/hosts/tvpaint/template.tvpp | Bin 0 -> 62370 bytes pype/resources/app_icons/tvpaint.png | Bin 0 -> 11859 bytes 4 files changed, 111 insertions(+) create mode 100644 pype/hooks/tvpaint/prelaunch.py create mode 100644 pype/hosts/tvpaint/__init__.py create mode 100644 pype/hosts/tvpaint/template.tvpp create mode 100644 pype/resources/app_icons/tvpaint.png diff --git a/pype/hooks/tvpaint/prelaunch.py b/pype/hooks/tvpaint/prelaunch.py new file mode 100644 index 0000000000..b8233e9c93 --- /dev/null +++ b/pype/hooks/tvpaint/prelaunch.py @@ -0,0 +1,110 @@ +import os +import shutil +from pype.lib import PypeHook +from pype.api import ( + Anatomy, + Logger +) +import getpass +import avalon.api + + +class TvpaintPrelaunchHook(PypeHook): + """ + Workfile preparation hook + """ + workfile_ext = "tvpp" + + def __init__(self, logger=None): + if not logger: + self.log = Logger().get_logger(self.__class__.__name__) + else: + self.log = logger + + self.signature = "( {} )".format(self.__class__.__name__) + + def execute(self, *args, env: dict = None) -> bool: + if not env: + env = os.environ + + # get context variables + project_name = env["AVALON_PROJECT"] + asset_name = env["AVALON_ASSET"] + task_name = env["AVALON_TASK"] + workdir = env["AVALON_WORKDIR"] + + # get workfile path + workfile_path = self.get_anatomy_filled( + workdir, project_name, asset_name, task_name) + + # create workdir if doesn't exist + os.makedirs(workdir, exist_ok=True) + self.log.info(f"Work dir is: `{workdir}`") + + # get last version of workfile + workfile_last = env.get("AVALON_LAST_WORKFILE") + self.log.debug(f"_ workfile_last: `{workfile_last}`") + + if workfile_last: + workfile = workfile_last + workfile_path = os.path.join(workdir, workfile) + + # copy workfile from template if doesnt exist any on path + if not os.path.isfile(workfile_path): + # try to get path from environment or use default + # from `pype.celation` dir + template_path = env.get("TVPAINT_TEMPLATE") or os.path.join( + env.get("PYPE_MODULE_ROOT"), + "pype/hosts/tvpaint/template.tvpp" + ) + self.log.info( + f"Creating workfile from template: `{template_path}`") + shutil.copy2( + os.path.normpath(template_path), + os.path.normpath(workfile_path) + ) + + self.log.info(f"Workfile to open: `{workfile_path}`") + + # adding compulsory environment var for openting file + env["PYPE_TVPAINT_PROJECT_FILE"] = workfile_path + + return True + + def get_anatomy_filled(self, workdir, project_name, asset_name, task_name): + host_name = "tvpaint" + dbcon = avalon.api.AvalonMongoDB() + dbcon.install() + dbcon.Session["AVALON_PROJECT"] = project_name + project_document = dbcon.find_one({"type": "project"}) + asset_document = dbcon.find_one({ + "type": "asset", + "name": asset_name + }) + dbcon.uninstall() + + asset_doc_parents = asset_document["data"].get("parents") + hierarchy = "/".join(asset_doc_parents) + + data = { + "project": { + "name": project_document["name"], + "code": project_document["data"].get("code") + }, + "task": task_name, + "asset": asset_name, + "app": host_name, + "hierarchy": hierarchy + } + anatomy = Anatomy(project_name) + extensions = avalon.api.HOST_WORKFILE_EXTENSIONS[host_name] + file_template = anatomy.templates["work"]["file"] + data.update({ + "version": 1, + "user": os.environ.get("PYPE_USERNAME") or getpass.getuser(), + "ext": extensions[0] + }) + + return avalon.api.last_workfile( + workdir, file_template, data, extensions, True + ) diff --git a/pype/hosts/tvpaint/__init__.py b/pype/hosts/tvpaint/__init__.py new file mode 100644 index 0000000000..8c93d93738 --- /dev/null +++ b/pype/hosts/tvpaint/__init__.py @@ -0,0 +1 @@ +kwargs = None diff --git a/pype/hosts/tvpaint/template.tvpp b/pype/hosts/tvpaint/template.tvpp new file mode 100644 index 0000000000000000000000000000000000000000..4bf05d35955c9e7c074a5af0f182d9f1b78bea8a GIT binary patch literal 62370 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 UA)46aA1n^MmsOFekunYWADD4edH?_b literal 0 HcmV?d00001 From e0cf53a6b254631f7de086bfffe3a11dff6adc1d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Oct 2020 16:51:03 +0200 Subject: [PATCH 39/42] feat(tvpaint): updating icon to the nicest ever --- pype/resources/app_icons/tvpaint.png | Bin 11859 -> 133822 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/pype/resources/app_icons/tvpaint.png b/pype/resources/app_icons/tvpaint.png index 8b33f9665b5e39115d546a636f70ec8bd9f8ee2c..adad6b2015851b0328f8f69cb1e57198d7210152 100644 GIT binary patch literal 133822 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%0A)RCGmJa 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
n1-$ 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 Lh`_o-iMjs)3HG^8 literal 11859 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 UA)46aA1n^MmsOFekunYWADD4edH?_b From b1501fbad5519d4cf6a9925de1519aaa750f0af0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 9 Oct 2020 16:51:37 +0200 Subject: [PATCH 40/42] feat(tvpaint): adding custom templates from project configs --- pype/hooks/tvpaint/prelaunch.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/pype/hooks/tvpaint/prelaunch.py b/pype/hooks/tvpaint/prelaunch.py index b8233e9c93..f3cc575721 100644 --- a/pype/hooks/tvpaint/prelaunch.py +++ b/pype/hooks/tvpaint/prelaunch.py @@ -13,7 +13,7 @@ class TvpaintPrelaunchHook(PypeHook): """ Workfile preparation hook """ - workfile_ext = "tvpp" + host_name = "tvpaint" def __init__(self, logger=None): if not logger: @@ -32,6 +32,7 @@ class TvpaintPrelaunchHook(PypeHook): asset_name = env["AVALON_ASSET"] task_name = env["AVALON_TASK"] workdir = env["AVALON_WORKDIR"] + extension = avalon.api.HOST_WORKFILE_EXTENSIONS[self.host_name][0] # get workfile path workfile_path = self.get_anatomy_filled( @@ -52,13 +53,29 @@ class TvpaintPrelaunchHook(PypeHook): # copy workfile from template if doesnt exist any on path if not os.path.isfile(workfile_path): # try to get path from environment or use default - # from `pype.celation` dir + # from `pype.hosts.tvpaint` dir template_path = env.get("TVPAINT_TEMPLATE") or os.path.join( env.get("PYPE_MODULE_ROOT"), "pype/hosts/tvpaint/template.tvpp" ) + + # try to get template from project config folder + proj_config_path = os.path.join( + env["PYPE_PROJECT_CONFIGS"], project_name) + if os.path.exists(proj_config_path): + self.log.info( + f"extension: `{extension}`") + template_file = next(( + f for f in os.listdir(proj_config_path) + if extension in os.path.splitext(f)[1] + )) + if template_file: + template_path = os.path.join( + proj_config_path, template_file) self.log.info( f"Creating workfile from template: `{template_path}`") + + # copy template to new destinantion shutil.copy2( os.path.normpath(template_path), os.path.normpath(workfile_path) @@ -72,7 +89,6 @@ class TvpaintPrelaunchHook(PypeHook): return True def get_anatomy_filled(self, workdir, project_name, asset_name, task_name): - host_name = "tvpaint" dbcon = avalon.api.AvalonMongoDB() dbcon.install() dbcon.Session["AVALON_PROJECT"] = project_name @@ -93,11 +109,11 @@ class TvpaintPrelaunchHook(PypeHook): }, "task": task_name, "asset": asset_name, - "app": host_name, + "app": self.host_name, "hierarchy": hierarchy } anatomy = Anatomy(project_name) - extensions = avalon.api.HOST_WORKFILE_EXTENSIONS[host_name] + extensions = avalon.api.HOST_WORKFILE_EXTENSIONS[self.host_name] file_template = anatomy.templates["work"]["file"] data.update({ "version": 1, From 10b04b8089f2a45752895ab9df05c22fc4e80ee0 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 12 Oct 2020 11:49:57 +0200 Subject: [PATCH 41/42] change list comprehension to for loop with if --- pype/hooks/tvpaint/prelaunch.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pype/hooks/tvpaint/prelaunch.py b/pype/hooks/tvpaint/prelaunch.py index f3cc575721..d616949254 100644 --- a/pype/hooks/tvpaint/prelaunch.py +++ b/pype/hooks/tvpaint/prelaunch.py @@ -63,12 +63,12 @@ class TvpaintPrelaunchHook(PypeHook): proj_config_path = os.path.join( env["PYPE_PROJECT_CONFIGS"], project_name) if os.path.exists(proj_config_path): - self.log.info( - f"extension: `{extension}`") - template_file = next(( - f for f in os.listdir(proj_config_path) - if extension in os.path.splitext(f)[1] - )) + + template_file = None + for f in os.listdir(proj_config_path): + if extension in os.path.splitext(f): + template_file = f + if template_file: template_path = os.path.join( proj_config_path, template_file) From b1687b86ed440cea599a53dfa90c6fe907c625bd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 14 Oct 2020 13:53:24 +0200 Subject: [PATCH 42/42] disable auto ensure_scene_settings --- pype/hosts/harmony/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pype/hosts/harmony/__init__.py b/pype/hosts/harmony/__init__.py index fbf5ca6f12..92434abc77 100644 --- a/pype/hosts/harmony/__init__.py +++ b/pype/hosts/harmony/__init__.py @@ -155,8 +155,11 @@ def check_inventory(): def application_launch(): - ensure_scene_settings() - check_inventory() + # FIXME: This is breaking server <-> client communication. + # It is now moved so it it manually called. + # ensure_scene_settings() + # check_inventory() + pass def export_template(backdrops, nodes, filepath):