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):