Skip to content

integrate_inputlinks

IntegrateInputLinksAYON

Bases: ContextPlugin

Connecting version level dependency links

It expects workfile instance is being published.

Source code in client/ayon_core/plugins/publish/integrate_inputlinks.py
 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
170
171
172
173
174
175
176
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
    """Connecting version level dependency links

    Handles links:
        - generative - what gets produced from workfile
        - reference - what was loaded into workfile

    It expects workfile instance is being published.
    """

    order = pyblish.api.IntegratorOrder + 0.2
    label = "Connect Dependency InputLinks AYON"

    def process(self, context: pyblish.api.Context):
        """Connect dependency links for all instances, globally

        Code steps:
        - filter instances that integrated version
            - have "versionEntity" entry in data
        - separate workfile instance within filtered instances
        - when workfile instance is available:
            - link all `loadedVersions` as input of the workfile
            - link workfile as input of all other integrated versions
        - link version's inputs if it's instance have "inputVersions" entry
        -

        inputVersions:
            The "inputVersions" in instance.data should be a list of
            version ids (str), which are the dependencies of the publishing
            instance that should be extracted from working scene by the DCC
            specific publish plugin.
        """

        workfile_instance, other_instances = self.split_instances(context)

        # Variable where links are stored in submethods
        new_links_by_type: LinksByType = collections.defaultdict(list)

        self.create_workfile_links(
            workfile_instance, other_instances, new_links_by_type)

        self.create_generative_links(other_instances, new_links_by_type)

        self.create_links_on_server(context, new_links_by_type)

    def split_instances(
        self,
        context: pyblish.api.Context
    ) -> tuple[Union[pyblish.api.Instance, None], list[pyblish.api.Instance]]:
        """Separates published instances into workfile and other.

        Returns:
            tuple[
                pyblish.plugin.Instance | None,
                list(pyblish.plugin.Instance)
            ]: workfile instance and list of other instances
        """
        workfile_instance = None
        other_instances = []

        for instance in context:
            # Skip inactive instances
            if not instance.data.get("publish", True):
                continue

            if not instance.data.get("versionEntity"):
                self.log.debug(
                    "Instance {} doesn't have version.".format(instance))
                continue

            product_base_type = instance.data.get("productBaseType")
            if not product_base_type:
                product_base_type = instance.data["productType"]

            if product_base_type == "workfile":
                workfile_instance = instance
            else:
                other_instances.append(instance)
        return workfile_instance, other_instances

    def add_link(
        self,
        new_links_by_type: LinksByType,
        link_type: str,
        input_id: str,
        output_id: str,
        data: dict[str, Any],
    ):
        """Add dependency link data into temporary variable.

        Args:
            new_links_by_type (
                dict[str, list[tuple[str, str, Optional[dict[str, Any]]]]]
            ): Object where output is stored.
            link_type (str): Type of link, one of 'reference' or 'generative'
            input_id (str): Input version id.
            output_id (str): Output version id.
            data (dict[str, Any]): Link metadata.
        """
        new_links_by_type[link_type].append(
            LinkPayload(
                input_id=input_id,
                output_id=output_id,
                data=data
            )
        )

    def create_workfile_links(
        self,
        workfile_instance: Optional[pyblish.api.Instance],
        other_instances: list[pyblish.api.Instance],
        new_links_by_type: LinksByType,
    ):
        """Adds links (generative and reference) for workfile.

        Args:
            workfile_instance (Optional[pyblish.plugin.Instance]): Published
                workfile.
            other_instances (list[pyblish.plugin.Instance]): Other published
                instances
            new_links_by_type (LinksByType): Dictionary collecting new created
                links by its type.
        """
        if workfile_instance is None:
            self.log.debug("No workfile in this publish session.")
            return

        workfile_version_id = workfile_instance.data["versionEntity"]["id"]
        # link workfile to all publishing versions
        for instance in other_instances:
            self.add_link(
                new_links_by_type,
                link_type="generative",
                input_id=workfile_version_id,
                output_id=instance.data["versionEntity"]["id"],
                data={}
            )

        loaded_versions = workfile_instance.context.data.get("loadedVersions")
        if not loaded_versions:
            return

        # link all loaded versions in scene into workfile
        for input_version in loaded_versions:
            input_version = InputVersion.from_value(input_version)
            self.add_link(
                new_links_by_type,
                link_type="reference",
                input_id=input_version.version_id,
                output_id=workfile_version_id,
                data=input_version.data
            )

    def create_generative_links(
        self,
        other_instances: list[pyblish.api.Instance],
        new_links_by_type: LinksByType,
    ):
        for instance in other_instances:
            input_versions = instance.data.get("inputVersions")
            if not input_versions:
                continue

            version_entity = instance.data["versionEntity"]
            for input_version in input_versions:
                input_version = InputVersion.from_value(input_version)
                self.add_link(
                    new_links_by_type,
                    link_type="generative",
                    input_id=input_version.version_id,
                    output_id=version_entity["id"],
                    data=input_version.data,
                )

    def _get_existing_links(self, project_name, link_type, entity_ids):
        """Find all existing links for given version ids.

        Args:
            project_name (str): Name of project.
            link_type (str): Type of link.
            entity_ids (set[str]): Set of version ids.

        Returns:
            dict[str, set[str]]: Existing links by version id.
        """

        output = collections.defaultdict(set)
        if not entity_ids:
            return output

        existing_in_links = ayon_api.get_versions_links(
            project_name, entity_ids, [link_type], "out"
        )

        for entity_id, links in existing_in_links.items():
            if not links:
                continue
            for link in links:
                output[entity_id].add(link["entityId"])
        return output

    def create_links_on_server(
        self,
        context: pyblish.api.Context,
        new_links: LinksByType,
    ):
        """Create new links on server."""
        if not new_links:
            return

        project_name: str = context.data["projectName"]

        # Make sure link types are available on server
        for link_type in new_links.keys():
            ayon_api.make_sure_link_type_exists(
                project_name, link_type, "version", "version"
            )

        # Create link themselves
        for link_type, link_payloads in new_links.items():
            # Make sure there are no duplicates of src > dst ids and merge
            # metadata if multiple links are found.
            mapping: dict[tuple[str, str], dict] = collections.defaultdict(
                dict
            )
            for link_payload in link_payloads:
                connection = (link_payload.input_id, link_payload.output_id)
                mapping[connection].update(link_payload.data)

            in_ids = {payload.input_id for payload in link_payloads}
            existing_links_by_in_id = self._get_existing_links(
                project_name, link_type, in_ids
            )

            for (input_id, output_id), data in mapping.items():
                existing_links = existing_links_by_in_id[input_id]
                # Skip creation of link if already exists
                # NOTE: AYON server does not support
                #     to have same links
                if output_id in existing_links:
                    continue
                create_link(
                    project_name,
                    link_type,
                    input_id,
                    "version",
                    output_id,
                    "version",
                    data=data,
                )

Add dependency link data into temporary variable.

Parameters:

Name Type Description Default
)

Object where output is stored.

required
link_type str

Type of link, one of 'reference' or 'generative'

required
input_id str

Input version id.

required
output_id str

Output version id.

required
data dict[str, Any]

Link metadata.

required
Source code in client/ayon_core/plugins/publish/integrate_inputlinks.py
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
def add_link(
    self,
    new_links_by_type: LinksByType,
    link_type: str,
    input_id: str,
    output_id: str,
    data: dict[str, Any],
):
    """Add dependency link data into temporary variable.

    Args:
        new_links_by_type (
            dict[str, list[tuple[str, str, Optional[dict[str, Any]]]]]
        ): Object where output is stored.
        link_type (str): Type of link, one of 'reference' or 'generative'
        input_id (str): Input version id.
        output_id (str): Output version id.
        data (dict[str, Any]): Link metadata.
    """
    new_links_by_type[link_type].append(
        LinkPayload(
            input_id=input_id,
            output_id=output_id,
            data=data
        )
    )

Create new links on server.

Source code in client/ayon_core/plugins/publish/integrate_inputlinks.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def create_links_on_server(
    self,
    context: pyblish.api.Context,
    new_links: LinksByType,
):
    """Create new links on server."""
    if not new_links:
        return

    project_name: str = context.data["projectName"]

    # Make sure link types are available on server
    for link_type in new_links.keys():
        ayon_api.make_sure_link_type_exists(
            project_name, link_type, "version", "version"
        )

    # Create link themselves
    for link_type, link_payloads in new_links.items():
        # Make sure there are no duplicates of src > dst ids and merge
        # metadata if multiple links are found.
        mapping: dict[tuple[str, str], dict] = collections.defaultdict(
            dict
        )
        for link_payload in link_payloads:
            connection = (link_payload.input_id, link_payload.output_id)
            mapping[connection].update(link_payload.data)

        in_ids = {payload.input_id for payload in link_payloads}
        existing_links_by_in_id = self._get_existing_links(
            project_name, link_type, in_ids
        )

        for (input_id, output_id), data in mapping.items():
            existing_links = existing_links_by_in_id[input_id]
            # Skip creation of link if already exists
            # NOTE: AYON server does not support
            #     to have same links
            if output_id in existing_links:
                continue
            create_link(
                project_name,
                link_type,
                input_id,
                "version",
                output_id,
                "version",
                data=data,
            )

Adds links (generative and reference) for workfile.

Parameters:

Name Type Description Default
workfile_instance Optional[Instance]

Published workfile.

required
other_instances list[Instance]

Other published instances

required
new_links_by_type LinksByType

Dictionary collecting new created links by its type.

required
Source code in client/ayon_core/plugins/publish/integrate_inputlinks.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def create_workfile_links(
    self,
    workfile_instance: Optional[pyblish.api.Instance],
    other_instances: list[pyblish.api.Instance],
    new_links_by_type: LinksByType,
):
    """Adds links (generative and reference) for workfile.

    Args:
        workfile_instance (Optional[pyblish.plugin.Instance]): Published
            workfile.
        other_instances (list[pyblish.plugin.Instance]): Other published
            instances
        new_links_by_type (LinksByType): Dictionary collecting new created
            links by its type.
    """
    if workfile_instance is None:
        self.log.debug("No workfile in this publish session.")
        return

    workfile_version_id = workfile_instance.data["versionEntity"]["id"]
    # link workfile to all publishing versions
    for instance in other_instances:
        self.add_link(
            new_links_by_type,
            link_type="generative",
            input_id=workfile_version_id,
            output_id=instance.data["versionEntity"]["id"],
            data={}
        )

    loaded_versions = workfile_instance.context.data.get("loadedVersions")
    if not loaded_versions:
        return

    # link all loaded versions in scene into workfile
    for input_version in loaded_versions:
        input_version = InputVersion.from_value(input_version)
        self.add_link(
            new_links_by_type,
            link_type="reference",
            input_id=input_version.version_id,
            output_id=workfile_version_id,
            data=input_version.data
        )

process(context)

Connect dependency links for all instances, globally

Code steps: - filter instances that integrated version - have "versionEntity" entry in data - separate workfile instance within filtered instances - when workfile instance is available: - link all loadedVersions as input of the workfile - link workfile as input of all other integrated versions - link version's inputs if it's instance have "inputVersions" entry -

inputVersions

The "inputVersions" in instance.data should be a list of version ids (str), which are the dependencies of the publishing instance that should be extracted from working scene by the DCC specific publish plugin.

Source code in client/ayon_core/plugins/publish/integrate_inputlinks.py
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
def process(self, context: pyblish.api.Context):
    """Connect dependency links for all instances, globally

    Code steps:
    - filter instances that integrated version
        - have "versionEntity" entry in data
    - separate workfile instance within filtered instances
    - when workfile instance is available:
        - link all `loadedVersions` as input of the workfile
        - link workfile as input of all other integrated versions
    - link version's inputs if it's instance have "inputVersions" entry
    -

    inputVersions:
        The "inputVersions" in instance.data should be a list of
        version ids (str), which are the dependencies of the publishing
        instance that should be extracted from working scene by the DCC
        specific publish plugin.
    """

    workfile_instance, other_instances = self.split_instances(context)

    # Variable where links are stored in submethods
    new_links_by_type: LinksByType = collections.defaultdict(list)

    self.create_workfile_links(
        workfile_instance, other_instances, new_links_by_type)

    self.create_generative_links(other_instances, new_links_by_type)

    self.create_links_on_server(context, new_links_by_type)

split_instances(context)

Separates published instances into workfile and other.

Returns:

Type Description
Union[Instance, None]

tuple[ pyblish.plugin.Instance | None, list(pyblish.plugin.Instance)

list[Instance]

]: workfile instance and list of other instances

Source code in client/ayon_core/plugins/publish/integrate_inputlinks.py
 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
def split_instances(
    self,
    context: pyblish.api.Context
) -> tuple[Union[pyblish.api.Instance, None], list[pyblish.api.Instance]]:
    """Separates published instances into workfile and other.

    Returns:
        tuple[
            pyblish.plugin.Instance | None,
            list(pyblish.plugin.Instance)
        ]: workfile instance and list of other instances
    """
    workfile_instance = None
    other_instances = []

    for instance in context:
        # Skip inactive instances
        if not instance.data.get("publish", True):
            continue

        if not instance.data.get("versionEntity"):
            self.log.debug(
                "Instance {} doesn't have version.".format(instance))
            continue

        product_base_type = instance.data.get("productBaseType")
        if not product_base_type:
            product_base_type = instance.data["productType"]

        if product_base_type == "workfile":
            workfile_instance = instance
        else:
            other_instances.append(instance)
    return workfile_instance, other_instances

Create link in AYON.

TODO Replace with 'ayon_api.create_link' when AYON launcher >= 1.5.2 is required by ayon-core.

Source code in client/ayon_core/plugins/publish/integrate_inputlinks.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def create_link(
    project_name: str,
    link_type_name: str,
    input_id: str,
    input_type: str,
    output_id: str,
    output_type: str,
    data: dict,
):
    """Create link in AYON.

    TODO Replace with 'ayon_api.create_link' when AYON launcher >= 1.5.2
        is required by ayon-core.

    """
    full_link_type_name = ayon_api.get_full_link_type_name(
        link_type_name, input_type, output_type)

    kwargs = {
        "input": input_id,
        "output": output_id,
        "linkType": full_link_type_name,
    }
    if data:
        kwargs["data"] = data

    response = ayon_api.post(
        f"projects/{project_name}/links", **kwargs
    )
    response.raise_for_status()