Skip to content

pipeline

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, ''))

application_launch()

Triggered after start of app

Source code in client/ayon_aftereffects/api/pipeline.py
172
173
174
def application_launch():
    """Triggered after start of app"""
    check_inventory()

cache_and_get_instances(creator)

Cache instances in shared data.

Storing all instances as a list as legacy instances might be still present. Args: creator (Creator): Plugin which would like to get instances from host. Returns: List[]: list of all instances stored in metadata

Source code in client/ayon_aftereffects/api/pipeline.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
def cache_and_get_instances(creator):
    """Cache instances in shared data.

    Storing all instances as a list as legacy instances might be still present.
    Args:
        creator (Creator): Plugin which would like to get instances from host.
    Returns:
        List[]: list of all instances stored in metadata
    """
    shared_key = "ayon.aftereffects.instances"
    if shared_key not in creator.collection_shared_data:
        creator.collection_shared_data[shared_key] = \
            creator.host.list_instances()
    return creator.collection_shared_data[shared_key]

check_inventory()

Checks loaded containers if they are of highest version

Source code in client/ayon_aftereffects/api/pipeline.py
217
218
219
220
221
222
223
224
225
226
227
228
229
def check_inventory():
    """Checks loaded containers if they are of highest version"""
    if not any_outdated_containers():
        return

    # Warn about outdated containers.
    _app = get_ayon_qt_app()

    message_box = QtWidgets.QMessageBox()
    message_box.setIcon(QtWidgets.QMessageBox.Warning)
    msg = "There are outdated containers in the scene."
    message_box.setText(msg)
    message_box.exec_()

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

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