Skip to content

api

Public API

Anything that isn't defined here is INTERNAL and unreliable for external use.

AfterEffectsHost

Bases: HostBase, IWorkfileHost, ILoadHost, IPublishHost

Source code in client/ayon_aftereffects/api/pipeline.py
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
class AfterEffectsHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
    name = "aftereffects"

    def __init__(self):
        self._stub = None
        super(AfterEffectsHost, self).__init__()

    @property
    def stub(self):
        """
            Handle pulling stub from PS to run operations on host
        Returns:
            (AEServerStub) or None
        """
        if self._stub:
            return self._stub

        try:
            stub = get_stub()  # only after Photoshop is up
        except ConnectionNotEstablishedYet:
            print("Not connected yet, ignoring")
            return

        self._stub = stub
        return self._stub

    def install(self):
        print("Installing AYON After Effects integration...")

        pyblish.api.register_host("aftereffects")
        pyblish.api.register_plugin_path(PUBLISH_PATH)

        register_loader_plugin_path(LOAD_PATH)
        register_creator_plugin_path(CREATE_PATH)
        register_workfile_build_plugin_path(WORKFILE_BUILD_PATH)

        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)

    # created instances section
    def list_instances(self):
        """List all created instances from current workfile which
        will be published.

        Pulls from File > File Info

        For Scene Inventory (Manage...)

        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") in {
                AYON_INSTANCE_ID, AVALON_INSTANCE_ID
            }:
                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 Scene Inventory

        Args:
            instance (dict): instance representation from Scene Inventory 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, ''))

stub property

Handle pulling stub from PS to run operations on host

Returns: (AEServerStub) or None

list_instances()

List all created instances from current workfile which will be published.

Pulls from File > File Info

For Scene Inventory (Manage...)

Returns:

Type Description

(list) of dictionaries matching instances format

Source code in client/ayon_aftereffects/api/pipeline.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def list_instances(self):
    """List all created instances from current workfile which
    will be published.

    Pulls from File > File Info

    For Scene Inventory (Manage...)

    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") in {
            AYON_INSTANCE_ID, AVALON_INSTANCE_ID
        }:
            instances.append(instance)
    return instances

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 Scene Inventory

Parameters:

Name Type Description Default
instance dict

instance representation from Scene Inventory model

required
Source code in client/ayon_aftereffects/api/pipeline.py
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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 Scene Inventory

    Args:
        instance (dict): instance representation from Scene Inventory 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, ''))

containerise(name, namespace, comp, context, loader=None, suffix='_CON')

Containerisation enables a tracking of version, author and origin for loaded assets.

Creates dictionary payloads that gets saved into file metadata. Each container contains of who loaded (loader) and members (single or multiple in case of background).

Parameters:

Name Type Description Default
name str

Name of resulting assembly

required
namespace str

Namespace under which to host container

required
comp AEItem

Composition to containerise

required
context dict

Asset information

required
loader str

Name of loader used to produce this container.

None
suffix str

Suffix of container, defaults to _CON.

'_CON'

Returns:

Name Type Description
container str

Name of container assembly

Source code in client/ayon_aftereffects/api/pipeline.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
def containerise(name,
                 namespace,
                 comp,
                 context,
                 loader=None,
                 suffix="_CON"):
    """
    Containerisation enables a tracking of version, author and origin
    for loaded assets.

    Creates dictionary payloads that gets saved into file metadata. Each
    container contains of who loaded (loader) and members (single or multiple
    in case of background).

    Arguments:
        name (str): Name of resulting assembly
        namespace (str): Namespace under which to host container
        comp (AEItem): Composition to containerise
        context (dict): Asset information
        loader (str, optional): Name of loader used to produce this container.
        suffix (str, optional): Suffix of container, defaults to `_CON`.

    Returns:
        container (str): Name of container assembly
    """
    data = {
        "schema": "ayon:container-3.0",
        "id": AYON_CONTAINER_ID,
        "name": name,
        "namespace": namespace,
        "loader": str(loader),
        "representation": context["representation"]["id"],
        "members": comp.members or [comp.id]
    }

    stub = get_stub()
    stub.imprint(comp.id, data)

    return comp

get_entity_attributes(entity)

Get attributes of folder or task entity.

Returns:

Name Type Description
dict dict[str, Union[float, int]]

Scene data.

Source code in client/ayon_aftereffects/api/lib.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def get_entity_attributes(entity: dict) -> dict[str, Union[float, int]]:
    """Get attributes of folder or task entity.

    Returns:
        dict: Scene data.

    """
    attrib: dict = entity["attrib"]
    fps = attrib.get("fps", 0)
    frame_start = attrib.get("frameStart", 0)
    frame_end = attrib.get("frameEnd", 0)
    handle_start = attrib.get("handleStart", 0)
    handle_end = attrib.get("handleEnd", 0)
    resolution_width = attrib.get("resolutionWidth", 0)
    resolution_height = attrib.get("resolutionHeight", 0)
    duration = (frame_end - frame_start + 1) + handle_start + handle_end

    return {
        "fps": fps,
        "frameStart": frame_start,
        "frameEnd": frame_end,
        "handleStart": handle_start,
        "handleEnd": handle_end,
        "resolutionWidth": resolution_width,
        "resolutionHeight": resolution_height,
        "duration": duration
    }

get_stub()

Convenience function to get server RPC stub to call methods directed
for host (Photoshop).
It expects already created connection, started from client.
Currently, created when panel is opened (PS: Window>Extensions>AYON)

:return: where functions could be called from

Source code in client/ayon_aftereffects/api/ws_stub.py
720
721
722
723
724
725
726
727
728
729
730
731
732
def get_stub():
    """
        Convenience function to get server RPC stub to call methods directed
        for host (Photoshop).
        It expects already created connection, started from client.
        Currently, created when panel is opened (PS: Window>Extensions>AYON)
    :return: <PhotoshopClientStub> where functions could be called from
    """
    ae_stub = AfterEffectsServerStub()
    if not ae_stub.client:
        raise ConnectionNotEstablishedYet("Connection is not created yet")

    return ae_stub

ls()

Yields containers from active AfterEffects document.

This is the host-equivalent of api.ls(), but instead of listing assets on disk, it lists assets already loaded in AE; once loaded they are called 'containers'. Used in Manage tool.

Containers could be on multiple levels, single images/videos/was as a FootageItem, or multiple items - backgrounds (folder with automatically created composition and all imported layers).

Yields:

Name Type Description
dict

container

Source code in client/ayon_aftereffects/api/pipeline.py
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
def ls():
    """Yields containers from active AfterEffects document.

    This is the host-equivalent of api.ls(), but instead of listing
    assets on disk, it lists assets already loaded in AE; once loaded
    they are called 'containers'. Used in Manage tool.

    Containers could be on multiple levels, single images/videos/was as a
    FootageItem, or multiple items - backgrounds (folder with automatically
    created composition and all imported layers).

    Yields:
        dict: container

    """
    try:
        stub = get_stub()  # only after AfterEffects is up
    except ConnectionNotEstablishedYet:
        print("Not connected yet, ignoring")
        return

    layers_meta = stub.get_metadata()
    for item in stub.get_items(comps=True,
                               folders=True,
                               footages=True):
        data = stub.read(item, layers_meta)
        # Skip non-tagged layers.
        if not data:
            continue

        # Filter to only containers.
        if "container" not in data["id"]:
            continue

        # Append transient data
        data["objectName"] = item.name.replace(stub.LOADED_ICON, '')
        data["layer"] = item
        yield data

maintained_selection()

Maintain selection during context.

Source code in client/ayon_aftereffects/api/lib.py
18
19
20
21
22
23
24
25
@contextlib.contextmanager
def maintained_selection():
    """Maintain selection during context."""
    selection = get_stub().get_selected_items(True, False, False)
    try:
        yield selection
    finally:
        pass

set_settings(frames, resolution, comp_ids=None, print_msg=True, entity=None)

Sets number of frames and resolution to selected comps.

Parameters:

Name Type Description Default
frames bool

True if set frame info

required
resolution bool

True if set resolution

required
comp_ids list[int]

specific composition ids, if empty it tries to look for currently selected

None
print_msg bool

True throw JS alert with msg

True
entity Optional[dict]

Entity to use attributes from to define the frame range, fps and resolution from. If not provided, current task entity is used.

None
Source code in client/ayon_aftereffects/api/lib.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
def set_settings(
        frames, resolution, comp_ids=None, print_msg=True, entity=None):
    """Sets number of frames and resolution to selected comps.

    Args:
        frames (bool): True if set frame info
        resolution (bool): True if set resolution
        comp_ids (list[int]): specific composition ids, if empty
            it tries to look for currently selected
        print_msg (bool): True throw JS alert with msg
        entity (Optional[dict]): Entity to use attributes from to define the
            frame range, fps and resolution from. If not provided, current
            task entity is used.
    """
    frame_start = frames_duration = fps = width = height = None

    if entity is None:
        entity = get_current_task_entity()
    settings = get_entity_attributes(entity)

    msg = ''
    if frames:
        frame_start = settings["frameStart"] - settings["handleStart"]
        frames_duration = settings["duration"]
        fps = settings["fps"]
        msg += f"frame start:{frame_start}, duration:{frames_duration}, "\
               f"fps:{fps}"
    if resolution:
        width = settings["resolutionWidth"]
        height = settings["resolutionHeight"]
        msg += f"width:{width} and height:{height}"

    stub = get_stub()
    if not comp_ids:
        comps = stub.get_selected_items(True, False, False)
        comp_ids = [comp.id for comp in comps]
    if not comp_ids:
        stub.print_msg("Select at least one composition to apply settings.")
        return

    for comp_id in comp_ids:
        msg = f"Setting for comp {comp_id} " + msg
        log.debug(msg)
        stub.set_comp_properties(comp_id, frame_start, frames_duration,
                                 fps, width, height)
        if print_msg:
            stub.print_msg(msg)