From 825a31f20b510013903c45d22cb161487ee8c267 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 20 Nov 2020 14:15:55 +0100 Subject: [PATCH] #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 697809363e..84dce39a41 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 @@ class AfterEffectsServerStub(): 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 @@ class AfterEffectsServerStub(): 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 858e2190c0..1944cf9937 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 25273ac136..0e33a26806 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 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): # 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 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): 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 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): # 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 @@ class CollectAERender(abstract_collect_render.AbstractCollectRender): 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 d4b6f11653..15d9e216fb 100644 --- a/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py +++ b/pype/plugins/aftereffects/publish/submit_aftereffects_deadline.py @@ -3,7 +3,6 @@ from pype.lib.abstract_submit_deadline import DeadlineJobInfo 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):