diff --git a/openpype/hosts/aftereffects/api/__init__.py b/openpype/hosts/aftereffects/api/__init__.py index 2ad1255d27..7b7590abc9 100644 --- a/openpype/hosts/aftereffects/api/__init__.py +++ b/openpype/hosts/aftereffects/api/__init__.py @@ -10,25 +10,10 @@ from .launch_logic import ( ) from .pipeline import ( + AfterEffectsHost, ls, get_asset_settings, - install, - uninstall, - list_instances, - remove_instance, - containerise, - get_context_data, - update_context_data, - get_context_title -) - -from .workio import ( - file_extensions, - has_unsaved_changes, - save_file, - open_file, - current_file, - work_root, + containerise ) from .lib import ( @@ -49,21 +34,7 @@ __all__ = [ # pipeline "ls", "get_asset_settings", - "install", - "uninstall", - "list_instances", - "remove_instance", "containerise", - "get_context_data", - "update_context_data", - "get_context_title", - - "file_extensions", - "has_unsaved_changes", - "save_file", - "open_file", - "current_file", - "work_root", # lib "maintained_selection", diff --git a/openpype/hosts/aftereffects/api/lib.py b/openpype/hosts/aftereffects/api/lib.py index 8cdf9c407e..7a73986633 100644 --- a/openpype/hosts/aftereffects/api/lib.py +++ b/openpype/hosts/aftereffects/api/lib.py @@ -26,9 +26,10 @@ def safe_excepthook(*args): def main(*subprocess_args): sys.excepthook = safe_excepthook - from openpype.hosts.aftereffects import api + from openpype.hosts.aftereffects.api import AfterEffectsHost - install_host(api) + host = AfterEffectsHost() + install_host(host) os.environ["OPENPYPE_LOG_NO_COLORS"] = "False" app = QtWidgets.QApplication([]) diff --git a/openpype/hosts/aftereffects/api/pipeline.py b/openpype/hosts/aftereffects/api/pipeline.py index 7026fe3f05..e3ea3d45fa 100644 --- a/openpype/hosts/aftereffects/api/pipeline.py +++ b/openpype/hosts/aftereffects/api/pipeline.py @@ -16,6 +16,12 @@ from openpype.pipeline import ( from openpype.pipeline.load import any_outdated_containers import openpype.hosts.aftereffects +from openpype.host import ( + HostBase, + IWorkfileHost, + ILoadHost, +) + from .launch_logic import get_stub, ConnectionNotEstablishedYet log = Logger.get_logger(__name__) @@ -30,27 +36,155 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -def install(): - print("Installing Pype config...") +class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost): + name = "aftereffects" - pyblish.api.register_host("aftereffects") - pyblish.api.register_plugin_path(PUBLISH_PATH) + def __init__(self): + self._stub = None + super(AfterEffectsHost, self).__init__() - register_loader_plugin_path(LOAD_PATH) - register_creator_plugin_path(CREATE_PATH) - log.info(PUBLISH_PATH) + @property + def stub(self): + """ + Handle pulling stub from PS to run operations on host + Returns: + (AEServerStub) or None + """ + if self._stub: + return self._stub - pyblish.api.register_callback( - "instanceToggled", on_pyblish_instance_toggled - ) + try: + stub = get_stub() # only after Photoshop is up + except ConnectionNotEstablishedYet: + print("Not connected yet, ignoring") + return - register_event_callback("application.launched", application_launch) + if not stub.get_active_document_name(): + return + self._stub = stub + return self._stub -def uninstall(): - pyblish.api.deregister_plugin_path(PUBLISH_PATH) - deregister_loader_plugin_path(LOAD_PATH) - deregister_creator_plugin_path(CREATE_PATH) + def install(self): + print("Installing Pype config...") + + pyblish.api.register_host("aftereffects") + pyblish.api.register_plugin_path(PUBLISH_PATH) + + register_loader_plugin_path(LOAD_PATH) + register_creator_plugin_path(CREATE_PATH) + log.info(PUBLISH_PATH) + + pyblish.api.register_callback( + "instanceToggled", on_pyblish_instance_toggled + ) + + register_event_callback("application.launched", application_launch) + + def get_workfile_extensions(self): + return [".aep"] + + def save_workfile(self, dst_path=None): + self.stub.saveAs(dst_path, True) + + def open_workfile(self, filepath): + self.stub.open(filepath) + + return True + + def get_current_workfile(self): + try: + full_name = get_stub().get_active_document_full_name() + if full_name and full_name != "null": + return os.path.normpath(full_name).replace("\\", "/") + except ValueError: + print("Nothing opened") + pass + + return None + + def get_containers(self): + return ls() + + def get_context_data(self): + meta = self.stub.get_metadata() + for item in meta: + if item.get("id") == "publish_context": + item.pop("id") + return item + + return {} + + def update_context_data(self, data, changes): + item = data + item["id"] = "publish_context" + self.stub.imprint(item["id"], item) + + def get_context_title(self): + """Returns title for Creator window""" + + project_name = legacy_io.Session["AVALON_PROJECT"] + asset_name = legacy_io.Session["AVALON_ASSET"] + task_name = legacy_io.Session["AVALON_TASK"] + return "{}/{}/{}".format(project_name, asset_name, task_name) + + # created instances section + def list_instances(self): + """List all created instances from current workfile which + will be published. + + Pulls from File > File Info + + For SubsetManager + + Returns: + (list) of dictionaries matching instances format + """ + stub = self.stub + if not stub: + return [] + + instances = [] + layers_meta = stub.get_metadata() + + for instance in layers_meta: + if instance.get("id") == "pyblish.avalon.instance": + instances.append(instance) + return instances + + def remove_instance(self, instance): + """Remove instance from current workfile metadata. + + Updates metadata of current file in File > File Info and removes + icon highlight on group layer. + + For SubsetManager + + Args: + instance (dict): instance representation from subsetmanager model + """ + stub = self.stub + + if not stub: + return + + inst_id = instance.get("instance_id") or instance.get("uuid") # legacy + if not inst_id: + log.warning("No instance identifier for {}".format(instance)) + return + + stub.remove_instance(inst_id) + + if instance.get("members"): + item = stub.get_item(instance["members"][0]) + if item: + stub.rename_item(item.id, + item.name.replace(stub.PUBLISH_ICON, '')) + + def uninstall(self): + pyblish.api.deregister_plugin_path(PUBLISH_PATH) + deregister_loader_plugin_path(LOAD_PATH) + deregister_creator_plugin_path(CREATE_PATH) def application_launch(): @@ -190,103 +324,3 @@ def containerise(name, return comp - -# created instances section -def list_instances(): - """ - List all created instances from current workfile which - will be published. - - Pulls from File > File Info - - For SubsetManager - - Returns: - (list) of dictionaries matching instances format - """ - stub = _get_stub() - if not stub: - return [] - - instances = [] - layers_meta = stub.get_metadata() - - for instance in layers_meta: - if instance.get("id") == "pyblish.avalon.instance": - instances.append(instance) - return instances - - -def remove_instance(instance): - """ - Remove instance from current workfile metadata. - - Updates metadata of current file in File > File Info and removes - icon highlight on group layer. - - For SubsetManager - - Args: - instance (dict): instance representation from subsetmanager model - """ - stub = _get_stub() - - if not stub: - return - - inst_id = instance.get("instance_id") or instance.get("uuid") # legacy - if not inst_id: - log.warning("No instance identifier for {}".format(instance)) - return - - stub.remove_instance(inst_id) - - if instance.get("members"): - item = stub.get_item(instance["members"][0]) - if item: - stub.rename_item(item.id, - item.name.replace(stub.PUBLISH_ICON, '')) - - -# new publisher section -def get_context_data(): - meta = _get_stub().get_metadata() - for item in meta: - if item.get("id") == "publish_context": - item.pop("id") - return item - - return {} - - -def update_context_data(data, changes): - item = data - item["id"] = "publish_context" - _get_stub().imprint(item["id"], item) - - -def get_context_title(): - """Returns title for Creator window""" - - project_name = legacy_io.Session["AVALON_PROJECT"] - asset_name = legacy_io.Session["AVALON_ASSET"] - task_name = legacy_io.Session["AVALON_TASK"] - return "{}/{}/{}".format(project_name, asset_name, task_name) - - -def _get_stub(): - """ - Handle pulling stub from PS to run operations on host - Returns: - (AEServerStub) or None - """ - try: - stub = get_stub() # only after Photoshop is up - except ConnectionNotEstablishedYet: - print("Not connected yet, ignoring") - return - - if not stub.get_active_document_name(): - return - - return stub diff --git a/openpype/hosts/aftereffects/api/workio.py b/openpype/hosts/aftereffects/api/workio.py deleted file mode 100644 index 18b40af5dc..0000000000 --- a/openpype/hosts/aftereffects/api/workio.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Host API required Work Files tool""" -import os - -from .launch_logic import get_stub - - -def file_extensions(): - return [".aep"] - - -def has_unsaved_changes(): - if _active_document(): - return not get_stub().is_saved() - - return False - - -def save_file(filepath): - get_stub().saveAs(filepath, True) - - -def open_file(filepath): - get_stub().open(filepath) - - return True - - -def current_file(): - try: - full_name = get_stub().get_active_document_full_name() - if full_name and full_name != "null": - return os.path.normpath(full_name).replace("\\", "/") - except ValueError: - print("Nothing opened") - pass - - return None - - -def work_root(session): - return os.path.normpath(session["AVALON_WORKDIR"]).replace("\\", "/") - - -def _active_document(): - # TODO merge with current_file - even in extension - document_name = None - try: - document_name = get_stub().get_active_document_name() - except ValueError: - print("Nothing opened") - pass - - return document_name diff --git a/openpype/hosts/aftereffects/plugins/create/create_render.py b/openpype/hosts/aftereffects/plugins/create/create_render.py index 1019709dd6..abf38dd7be 100644 --- a/openpype/hosts/aftereffects/plugins/create/create_render.py +++ b/openpype/hosts/aftereffects/plugins/create/create_render.py @@ -7,6 +7,7 @@ from openpype.pipeline import ( CreatorError, legacy_io, ) +from openpype.hosts.aftereffects.api import AfterEffectsHost class RenderCreator(Creator): @@ -23,12 +24,13 @@ class RenderCreator(Creator): ["create"] ["RenderCreator"] ["defaults"]) + self.host = AfterEffectsHost() def get_icon(self): return resources.get_openpype_splash_filepath() def collect_instances(self): - for instance_data in api.list_instances(): + for instance_data in self.host.list_instances(): # legacy instances have family=='render' or 'renderLocal', use them creator_id = (instance_data.get("creator_identifier") or instance_data.get("family", '').replace("Local", '')) @@ -46,7 +48,7 @@ class RenderCreator(Creator): def remove_instances(self, instances): for instance in instances: - api.remove_instance(instance) + self.host.remove_instance(instance) self._remove_instance_from_context(instance) def create(self, subset_name, data, pre_create_data): diff --git a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py index f82d15b3c9..8e002b78ea 100644 --- a/openpype/hosts/aftereffects/plugins/create/workfile_creator.py +++ b/openpype/hosts/aftereffects/plugins/create/workfile_creator.py @@ -5,6 +5,7 @@ from openpype.pipeline import ( CreatedInstance, legacy_io, ) +from openpype.hosts.aftereffects.api import AfterEffectsHost class AEWorkfileCreator(AutoCreator): @@ -17,7 +18,7 @@ class AEWorkfileCreator(AutoCreator): return [] def collect_instances(self): - for instance_data in api.list_instances(): + for instance_data in AfterEffectsHost().list_instances(): creator_id = instance_data.get("creator_identifier") if creator_id == self.identifier: subset_name = instance_data["subset"]