Skip to content

collect_render

CollectAERender

Bases: AbstractCollectRender

Prepares RenderInstance.

RenderInstance is meant to replace simple dictionaries to provide code assist and typing. (Currently used only in AE, Harmony though.)

This must run after collect_review, but before Deadline plugins (which should be run only on renderable instances.)

Source code in client/ayon_aftereffects/plugins/publish/collect_render.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 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
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
class CollectAERender(publish.AbstractCollectRender):
    """Prepares RenderInstance.

    RenderInstance is meant to replace simple dictionaries to provide code
    assist and typing. (Currently used only in AE, Harmony though.)

    This must run after `collect_review`, but before Deadline plugins (which
    should be run only on renderable instances.)
    """

    order = pyblish.api.CollectorOrder + 0.125
    label = "Collect After Effects Render Layers"
    hosts = ["aftereffects"]

    padding_width = 6
    rendered_extension = 'png'

    _stub = None

    @classmethod
    def get_stub(cls):
        if not cls._stub:
            cls._stub = get_stub()
        return cls._stub

    def get_instances(
        self, context: pyblish.api.Context
    ) -> list[AERenderInstance]:
        instances = []

        app_version = CollectAERender.get_stub().get_app_version()
        app_version = app_version[0:4]

        current_file = context.data["currentFile"]
        version = context.data["version"]

        project_entity = context.data["projectEntity"]

        compositions = CollectAERender.get_stub().get_items(True)
        compositions_by_id = {item.id: item for item in compositions}
        for inst in context:
            if not inst.data.get("active", True):
                continue

            product_type = inst.data["productType"]
            if product_type not in ["render", "renderLocal"]:  # legacy
                continue

            comp_id = int(inst.data["members"][0])

            comp_info = CollectAERender.get_stub().get_comp_properties(
                comp_id)

            if not comp_info:
                self.log.warning("Orphaned instance, deleting metadata")
                inst_id = inst.data.get("instance_id") or str(comp_id)
                CollectAERender.get_stub().remove_instance(inst_id)
                continue

            frame_start = comp_info.frameStart
            frame_end = round(comp_info.frameStart +
                              comp_info.framesDuration) - 1
            fps = comp_info.frameRate
            # TODO add resolution when supported by extension

            task_name = inst.data.get("task")

            render_q = CollectAERender.get_stub().get_render_info(comp_id)
            if not render_q:
                raise PublishValidationError(
                    "No file extension set in Render Queue")
            render_item = render_q[0]

            product_type = "render"
            instance_families = inst.data.get("families", [])
            instance_families.append(product_type)
            product_name = inst.data["productName"]
            instance = AERenderInstance(
                productType=product_type,
                family=product_type,
                families=instance_families,
                version=version,
                time="",
                source=current_file,
                label="{} - {}".format(product_name, product_type),
                productName=product_name,
                folderPath=inst.data["folderPath"],
                task=task_name,
                attachTo=False,
                setMembers='',
                publish=True,
                name=product_name,
                resolutionWidth=render_item.width,
                resolutionHeight=render_item.height,
                pixelAspect=1,
                tileRendering=False,
                tilesX=0,
                tilesY=0,
                review="review" in instance_families,
                frameStart=frame_start,
                frameEnd=frame_end,
                frameStep=1,
                fps=fps,
                app_version=app_version,
                publish_attributes=inst.data.get("publish_attributes", {}),
                # one path per output module, could be multiple
                render_queue_file_paths=[item.file_name for item in render_q],
                # The source instance this render instance replaces
                source_instance=inst
            )

            comp = compositions_by_id.get(comp_id)
            if not comp:
                raise ValueError("There is no composition for item {}".
                                 format(comp_id))
            instance.outputDir = self._get_output_dir(instance)
            instance.comp_name = comp.name
            instance.comp_id = comp_id

            creator_attributes = inst.data["creator_attributes"]
            if creator_attributes["render_target"] == "local":
                # for local renders
                instance = self._update_for_local(instance, project_entity)
            elif creator_attributes["render_target"] == "farm":
                fam = "render.farm"
                if fam not in instance.families:
                    instance.families.append(fam)
                instance.renderer = "aerender"
                instance.farm = True  # to skip integrate
                if "review" in instance.families:
                    # to skip ExtractReview locally
                    instance.families.remove("review")

            instances.append(instance)

        return instances

    def get_expected_files(self, render_instance):
        """
            Returns list of rendered files that should be created by
            Deadline. These are not published directly, they are source
            for later 'submit_publish_job'.

        Args:
            render_instance (AERenderInstance): to pull anatomy and parts used
                in url

        Returns:
            (list) of absolute urls to rendered file
        """
        start = render_instance.frameStart
        end = render_instance.frameEnd

        base_dir = self._get_output_dir(render_instance)
        expected_files = []
        for file_name in render_instance.render_queue_file_paths:
            _, ext = os.path.splitext(os.path.basename(file_name))
            ext = ext.replace('.', '')
            version_str = "v{:03d}".format(render_instance.version)
            if "#" not in file_name:  # single frame (mov)
                file_name = "{}_{}.{}".format(
                    render_instance.productName,
                    version_str,
                    ext
                )
                file_path = os.path.join(base_dir, file_name)
                expected_files.append(file_path)
            else:
                for frame in range(start, end + 1):
                    file_name = "{}_{}.{}.{}".format(
                        render_instance.productName,
                        version_str,
                        str(frame).zfill(self.padding_width),
                        ext
                    )

                    file_path = os.path.join(base_dir, file_name)
                    expected_files.append(file_path)
        return expected_files

    def _get_output_dir(self, render_instance):
        """Return dir path of rendered files, used in submit_publish_job
        for metadata.json location. Should be in separate folder inside work
        area.

        Args:
            render_instance (AERenderInstance): The render instance.

        Returns:
            (str): absolute path to rendered files
        """
        # render to folder of workfile
        base_dir = os.path.dirname(render_instance.source)
        file_name, _ = os.path.splitext(
            os.path.basename(render_instance.source))
        base_dir = os.path.join(base_dir, 'renders', 'aftereffects', file_name)

        # for submit_publish_job
        return base_dir

    def _update_for_local(self, instance, project_entity):
        """Update old saved instances to current publishing format"""
        instance.stagingDir = tempfile.mkdtemp()
        instance.projectEntity = project_entity
        fam = "render.local"
        if fam not in instance.families:
            instance.families.append(fam)

        return instance

get_expected_files(render_instance)

Returns list of rendered files that should be created by
Deadline. These are not published directly, they are source
for later 'submit_publish_job'.

Parameters:

Name Type Description Default
render_instance AERenderInstance

to pull anatomy and parts used in url

required

Returns:

Type Description

(list) of absolute urls to rendered file

Source code in client/ayon_aftereffects/plugins/publish/collect_render.py
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
def get_expected_files(self, render_instance):
    """
        Returns list of rendered files that should be created by
        Deadline. These are not published directly, they are source
        for later 'submit_publish_job'.

    Args:
        render_instance (AERenderInstance): to pull anatomy and parts used
            in url

    Returns:
        (list) of absolute urls to rendered file
    """
    start = render_instance.frameStart
    end = render_instance.frameEnd

    base_dir = self._get_output_dir(render_instance)
    expected_files = []
    for file_name in render_instance.render_queue_file_paths:
        _, ext = os.path.splitext(os.path.basename(file_name))
        ext = ext.replace('.', '')
        version_str = "v{:03d}".format(render_instance.version)
        if "#" not in file_name:  # single frame (mov)
            file_name = "{}_{}.{}".format(
                render_instance.productName,
                version_str,
                ext
            )
            file_path = os.path.join(base_dir, file_name)
            expected_files.append(file_path)
        else:
            for frame in range(start, end + 1):
                file_name = "{}_{}.{}.{}".format(
                    render_instance.productName,
                    version_str,
                    str(frame).zfill(self.padding_width),
                    ext
                )

                file_path = os.path.join(base_dir, file_name)
                expected_files.append(file_path)
    return expected_files