From 825a31f20b510013903c45d22cb161487ee8c267 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 Nov 2020 14:15:55 +0100 Subject: [PATCH 1/5] #735 - fixes for After Effects Changed collect_render family to 'render' to get correct name from Creator tool Changed outputDir to point to rendered location for metadata.json file Pulling startFrame, endFrame from AE Added files for handling this to stub --- .../stubs/aftereffects_server_stub.py | 50 +++++++++++++++++++ .../aftereffects/create/create_render.py | 45 ++++++++--------- .../aftereffects/publish/collect_render.py | 45 +++++++++-------- .../publish/submit_aftereffects_deadline.py | 4 +- 4 files changed, 94 insertions(+), 50 deletions(-) diff --git a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py index 697809363e2..84dce39a411 100644 --- a/pype/modules/websocket_server/stubs/aftereffects_server_stub.py +++ b/pype/modules/websocket_server/stubs/aftereffects_server_stub.py @@ -205,6 +205,19 @@ def replace_item(self, item, path, item_name): item_id=item.id, path=path, item_name=item_name)) + def rename_item(self, item, item_name): + """ Replace item with item_name + + Args: + item (dict): + item_name (string): label on item in Project list + + """ + self.websocketserver.call(self.client.call + ('AfterEffects.rename_item', + item_id=item.id, + item_name=item_name)) + def delete_item(self, item): """ Deletes FootageItem with new file Args: @@ -234,6 +247,43 @@ def set_label_color(self, item_id, color_idx): color_idx=color_idx )) + def get_work_area(self, item_id): + """ Get work are information for render purposes + Args: + item_id (int): + + """ + res = self.websocketserver.call(self.client.call + ('AfterEffects.get_work_area', + item_id=item_id + )) + + records = self._to_records(res) + if records: + return records.pop() + + log.debug("Couldn't get work area") + + def set_work_area(self, item, start, duration, frame_rate): + """ + Set work area to predefined values (from Ftrack). + Work area directs what gets rendered. + Beware of rounding, AE expects seconds, not frames directly. + + Args: + item (dict): + start (float): workAreaStart in seconds + duration (float): in seconds + frame_rate (float): frames in seconds + """ + self.websocketserver.call(self.client.call + ('AfterEffects.set_work_area', + item_id=item.id, + start=start, + duration=duration, + frame_rate=frame_rate + )) + def save(self): """ Saves active document diff --git a/pype/plugins/aftereffects/create/create_render.py b/pype/plugins/aftereffects/create/create_render.py index 858e2190c0a..1944cf9937c 100644 --- a/pype/plugins/aftereffects/create/create_render.py +++ b/pype/plugins/aftereffects/create/create_render.py @@ -12,41 +12,36 @@ class CreateRender(api.Creator): name = "renderDefault" label = "Render on Farm" - family = "render.farm" + family = "render" def process(self): - # Photoshop can have multiple LayerSets with the same name, which does - # not work with Avalon. - txt = "Instance with name \"{}\" already exists.".format(self.name) stub = aftereffects.stub() # only after After Effects is up - for layer in stub.get_items(comps=True, - folders=False, - footages=False): - if self.name.lower() == layer.name.lower(): - msg = Qt.QtWidgets.QMessageBox() - msg.setIcon(Qt.QtWidgets.QMessageBox.Warning) - msg.setText(txt) - msg.exec_() - return False - log.debug("options:: {}".format(self.options)) - print("options:: {}".format(self.options)) if (self.options or {}).get("useSelection"): - log.debug("useSelection") - print("useSelection") items = stub.get_selected_items(comps=True, folders=False, footages=False) else: - items = stub.get_items(comps=True, - folders=False, - footages=False) - log.debug("items:: {}".format(items)) - print("items:: {}".format(items)) + self._show_msg("Please select only single composition at time.") + return False + if not items: - raise ValueError("Nothing to create. Select composition " + - "if 'useSelection' or create at least " + - "one composition.") + self._show_msg("Nothing to create. Select composition " + + "if 'useSelection' or create at least " + + "one composition.") + return False for item in items: + txt = "Instance with name \"{}\" already exists.".format(self.name) + if self.name.lower() == item.name.lower(): + self._show_msg(txt) + return False + stub.imprint(item, self.data) stub.set_label_color(item.id, 14) # Cyan options 0 - 16 + stub.rename_item(item, self.data["subset"]) + + def _show_msg(self, txt): + msg = Qt.QtWidgets.QMessageBox() + msg.setIcon(Qt.QtWidgets.QMessageBox.Warning) + msg.setText(txt) + msg.exec_() diff --git a/pype/plugins/aftereffects/publish/collect_render.py b/pype/plugins/aftereffects/publish/collect_render.py index 25273ac1366..0e33a268062 100644 --- a/pype/plugins/aftereffects/publish/collect_render.py +++ b/pype/plugins/aftereffects/publish/collect_render.py @@ -1,7 +1,6 @@ from pype.lib import abstract_collect_render from pype.lib.abstract_collect_render import RenderInstance import pyblish.api -import copy import attr import os @@ -38,10 +37,20 @@ def get_instances(self, context): # loaded asset container skip it if schema and 'container' in schema: continue - if inst["family"] == "render.farm" and inst["active"]: + + work_area_info = aftereffects.stub().get_work_area(int(item_id)) + frameStart = round(float(work_area_info.workAreaStart) * + float(work_area_info.frameRate)) + + frameEnd = round(float(work_area_info.workAreaStart) * + float(work_area_info.frameRate) + + float(work_area_info.workAreaDuration) * + float(work_area_info.frameRate)) + + if inst["family"] == "render" and inst["active"]: instance = AERenderInstance( - family=inst["family"], - families=[inst["family"]], + family="render.farm", # other way integrate would catch it + families=["render.farm"], version=version, time="", source=current_file, @@ -63,8 +72,8 @@ def get_instances(self, context): tileRendering=False, tilesX=0, tilesY=0, - frameStart=int(asset_entity["data"]["frameStart"]), - frameEnd=int(asset_entity["data"]["frameEnd"]), + frameStart=frameStart, + frameEnd=frameEnd, frameStep=1, toBeRenderedOn='deadline' ) @@ -101,6 +110,7 @@ def get_expected_files(self, render_instance): # render to folder of workfile base_dir = os.path.dirname(render_instance.source) + base_dir = os.path.join(base_dir, 'renders', 'aftereffects') expected_files = [] for frame in range(start, end + 1): path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( @@ -116,26 +126,17 @@ def get_expected_files(self, render_instance): def _get_output_dir(self, render_instance): """ - Returns dir path of published asset. Required for - 'submit_publish_job'. - - It is different from rendered files (expectedFiles), these are - collected first in some 'staging' area, published later. + Returns dir path of rendered files, used in submit_publish_job + for metadata.json location Args: - render_instance (RenderInstance): to pull anatomy and parts used - in url + render_instance (RenderInstance): Returns: - (str): absolute path to published files + (str): absolute path to rendered files """ - anatomy = render_instance._anatomy - anatomy_data = copy.deepcopy(render_instance.anatomyData) - anatomy_data["family"] = render_instance.family - anatomy_data["version"] = render_instance.version - anatomy_data["subset"] = render_instance.subset - - anatomy_filled = anatomy.format(anatomy_data) + base_dir = os.path.dirname(render_instance.source) + base_dir = os.path.join(base_dir, 'renders', 'aftereffects') # for submit_publish_job - return anatomy_filled["render"]["folder"] + return base_dir diff --git a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py index d4b6f116531..15d9e216fb7 100644 --- a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py +++ b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py @@ -3,7 +3,6 @@ import pyblish.api import os import attr -import json import getpass from avalon import api @@ -17,7 +16,6 @@ class DeadlinePluginInfo(): StartupDirectory = attr.ib(default=None) Arguments = attr.ib(default=None) ProjectPath = attr.ib(default=None) - SceneFile = attr.ib(default=None) AWSAssetFile0 = attr.ib(default=None) Version = attr.ib(default=None) @@ -27,7 +25,7 @@ class AfterEffectsSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline label = "Submit AE to Deadline" order = pyblish.api.IntegratorOrder hosts = ["aftereffects"] - families = ["render.farm"] + families = ["render.farm"] # cannot be "render' as that is integrated use_published = False def get_job_info(self): From 6cd0e4651119458bb02421c51005802b994487f5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 Nov 2020 18:29:45 +0100 Subject: [PATCH 2/5] #735 - added file name to render folder --- pype/plugins/aftereffects/publish/collect_render.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/pype/plugins/aftereffects/publish/collect_render.py b/pype/plugins/aftereffects/publish/collect_render.py index 0e33a268062..13ffc3f208d 100644 --- a/pype/plugins/aftereffects/publish/collect_render.py +++ b/pype/plugins/aftereffects/publish/collect_render.py @@ -108,9 +108,7 @@ def get_expected_files(self, render_instance): start = render_instance.frameStart end = render_instance.frameEnd - # render to folder of workfile - base_dir = os.path.dirname(render_instance.source) - base_dir = os.path.join(base_dir, 'renders', 'aftereffects') + base_dir = self._get_output_dir(render_instance) expected_files = [] for frame in range(start, end + 1): path = os.path.join(base_dir, "{}_{}_{}.{}.{}".format( @@ -127,7 +125,8 @@ def get_expected_files(self, render_instance): def _get_output_dir(self, render_instance): """ Returns dir path of rendered files, used in submit_publish_job - for metadata.json location + for metadata.json location. + Should be in separate folder inside of work area. Args: render_instance (RenderInstance): @@ -135,8 +134,11 @@ def _get_output_dir(self, render_instance): Returns: (str): absolute path to rendered files """ + # render to folder of workfile base_dir = os.path.dirname(render_instance.source) - base_dir = os.path.join(base_dir, 'renders', 'aftereffects') + file_name, _ = os.path.splitext( + os.path.basename(render_instance.source)) + base_dir = os.path.join(base_dir, 'renders', 'aftereffects', file_name) # for submit_publish_job return base_dir From ac70c0515ad3438d3d12c0b1a787d34d77ac65d7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 Nov 2020 20:04:21 +0100 Subject: [PATCH 3/5] #735 - changed resolving of review --- .../global/publish/submit_publish_job.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index bc644987dee..77a88161174 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -133,7 +133,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): families = ["render.farm", "prerener", "renderlayer", "imagesequence", "vrayscene"] - aov_filter = {"maya": ["beauty"]} + aov_filter = {"maya": [r".+(?:\.|_)([Bb]eauty)(?:\.|_).*"], + "aftereffects": [r".*"], # for everything from AE + "celaction": [r".*"]} enviro_filter = [ "FTRACK_API_USER", @@ -447,8 +449,13 @@ def _create_instances_for_aov(self, instance_data, exp_files): preview = False if app in self.aov_filter.keys(): - if aov in self.aov_filter[app]: - preview = True + for aov_pattern in self.aov_filter[app]: + if re.match( + aov_pattern, + aov + ): + preview = True + break new_instance = copy(instance_data) new_instance["subset"] = subset_name @@ -513,29 +520,26 @@ def _get_representations(self, instance, exp_files): collections, remainders = clique.assemble(exp_files) bake_render_path = instance.get("bakeRenderPath", []) + print('@@@@ collections {}'.format(collections)) # create representation for every collected sequence for collection in collections: ext = collection.tail.lstrip(".") preview = False # if filtered aov name is found in filename, toggle it for # preview video rendering - for app in self.aov_filter: + for app in self.aov_filter.keys(): if os.environ.get("AVALON_APP", "") == app: for aov in self.aov_filter[app]: if re.match( - r".+(?:\.|_)({})(?:\.|_).*".format(aov), + aov, list(collection)[0] ): preview = True break - break if bake_render_path: preview = False - if "celaction" in pyblish.api.registered_hosts(): - preview = True - staging = os.path.dirname(list(collection)[0]) success, rootless_staging_dir = ( self.anatomy.find_root_template_from_path(staging) From ad321bb1e79330f839cf649a7cb69d40a8f96400 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 Nov 2020 20:07:36 +0100 Subject: [PATCH 4/5] Hound --- pype/plugins/global/publish/submit_publish_job.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 77a88161174..5fce0af35a0 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -450,8 +450,7 @@ def _create_instances_for_aov(self, instance_data, exp_files): preview = False if app in self.aov_filter.keys(): for aov_pattern in self.aov_filter[app]: - if re.match( - aov_pattern, + if re.match(aov_pattern, aov ): preview = True @@ -520,7 +519,6 @@ def _get_representations(self, instance, exp_files): collections, remainders = clique.assemble(exp_files) bake_render_path = instance.get("bakeRenderPath", []) - print('@@@@ collections {}'.format(collections)) # create representation for every collected sequence for collection in collections: ext = collection.tail.lstrip(".") From a1bf6916a764a848a871dd613c5d106dc175bf67 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 23 Nov 2020 10:18:49 +0100 Subject: [PATCH 5/5] remove redundant 'review' data --- pype/plugins/global/publish/submit_publish_job.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 5fce0af35a0..256bf016653 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -559,7 +559,7 @@ def _get_representations(self, instance, exp_files): # If expectedFile are absolute, we need only filenames "stagingDir": staging, "fps": instance.get("fps"), - "tags": ["review", "preview"] if preview else [], + "tags": ["review"] if preview else [], } # poor man exclusion @@ -711,8 +711,7 @@ def process(self, instance): "resolutionWidth": data.get("resolutionWidth", 1920), "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), - "jobBatchName": data.get("jobBatchName", ""), - "review": data.get("review", True) + "jobBatchName": data.get("jobBatchName", "") } if "prerender" in instance.data["families"]: