Skip to content

validate_renderpasses

ValidateRenderPasses

Bases: OptionalPyblishPluginMixin, InstancePlugin

Validates Render Passes before farm submission

Source code in client/ayon_max/plugins/publish/validate_renderpasses.py
 22
 23
 24
 25
 26
 27
 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
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
class ValidateRenderPasses(OptionalPyblishPluginMixin,
                           pyblish.api.InstancePlugin):
    """Validates Render Passes before farm submission
    """

    order = ValidateContentsOrder
    families = ["maxrender", "renderpreset"]
    hosts = ["max"]
    label = "Validate Render Passes"
    actions = [RepairAction]

    settings_category = "max"

    def process(self, instance):
        if not self.is_active(instance.data):
            return
        invalid = self.get_invalid(instance)
        if invalid:
            bullet_point_invalid_statement = "\n".join(
                f"- {err_type}: {filepath}" for err_type, filepath
                in invalid
            )
            report = (
                "Invalid render passes found.\n\n"
                f"{bullet_point_invalid_statement}\n\n"
                "You can use repair action to fix the invalid filepath."
            )
            raise PublishValidationError(
                report, title="Invalid Render Passes")

    @classmethod
    def get_invalid(cls, instance):
        """Function to get invalid beauty render outputs and
        render elements.

        1. Check Render Output Folder matches the name of
           the current Max Scene, e.g.
             The name of the current Max scene:
               John_Doe.max
             The expected render output directory:
               {root[work]}/{project[name]}/{hierarchy}/{asset}/
               work/{task[name]}/render/3dsmax/John_Doe/

        2. Check image extension(s) of the render output(s)
           matches the image format in OP/AYON setting, e.g.
               The current image format in settings: png
               The expected render outputs: John_Doe.png

        3. Check filename of render element ends with the name of
           render element from the 3dsMax Render Element Manager.
           e.g. The name of render element: RsCryptomatte
            The expected filename: {InstanceName}_RsCryptomatte.png

        Args:
            instance (pyblish.api.Instance): instance
            workfile_name (str): filename of the Max scene

        Returns:
            list: list of invalid filename which doesn't match
                with the project name
        """
        invalid = []
        project_settings = instance.context.data["project_settings"]
        default_render_folder = Path(
            get_default_render_folder(project_settings)
        )
        render_output = Path(rt.rendOutputFilename)
        render_dir = render_output.parent
        if default_render_folder != render_dir.parent:
            invalid.append(("Invalid render output folder",
                            os.path.dirname(rt.rendOutputFilename)))

        invalid_local_render_output = cls.get_invalid_local_render_output(instance)
        invalid.extend(invalid_local_render_output)
        renderer = instance.data.get("renderer")
        if not renderer:
            renderer_class = get_current_renderer()
            renderer = str(renderer_class).split(":")[0]

        beauty_fname = render_output.name
        beauty_name, ext = os.path.splitext(beauty_fname)
        invalid_filenames = cls.get_invalid_filenames(
            instance, beauty_name, ext)
        invalid.extend(invalid_filenames)
        invalid_image_format = cls.get_invalid_image_format(
            instance, ext.lstrip("."))
        invalid.extend(invalid_image_format)

        if is_supported_renderer(renderer):
            render_elem = rt.maxOps.GetCurRenderElementMgr()
            render_elem_num = render_elem.NumRenderElements()
            for i in range(render_elem_num):
                renderlayer_name = render_elem.GetRenderElement(i)
                renderpass = str(renderlayer_name).rsplit(":", 1)[-1]
                rend_file = render_elem.GetRenderElementFilename(i)
                if not rend_file:
                    continue
                render_filename = os.path.basename(rend_file)
                rend_fname, ext = os.path.splitext(render_filename)
                invalid_image_format = cls.get_invalid_image_format(
                    instance, ext)
                invalid_filenames = cls.get_invalid_filenames(
                    instance, rend_fname, ext, renderpass=renderpass,
                    render_filename=render_filename)
                invalid.extend(invalid_filenames)
                invalid.extend(invalid_image_format)

        elif renderer == "Arnold":
            cls.log.debug(
                "Renderpass validation does not support Arnold yet,"
                " validation skipped...")
        elif renderer.startswith("V_Ray_"):
            invalid_settings = cls.get_invalid_vray_settings(
                instance, renderer, ext, project_settings)
            invalid.extend(invalid_settings)
        else:
            cls.log.debug(
                "Skipping render element validation "
                f"for renderer: {renderer}")
        return invalid

    @classmethod
    def get_invalid_filenames(
        cls, instance, file_name,
        ext, renderpass=None,
        render_filename=None):
        """Function to get invalid filenames from render outputs.

        Args:
            instance (pyblish.api.Instance): instance
            file_name (str): name of the file
            ext (str): image extension
            renderpass (str, optional): name of the renderpass.
                Defaults to None.
            render_filename(str, optional): render filename

        Returns:
            list: invalid filenames
        """
        invalid = []
        if instance.name not in file_name:
            cls.log.error("The renderpass filename should contain the instance name.")
            invalid.append(("Invalid instance name",
                            file_name))
        # TODO: check on the renderpass if _camera has been included when the multi-camera options disabled.
        if renderpass is not None and render_filename is not None:
            renderpass_token = f"{renderpass}.{ext}"
            if not render_filename.endswith(renderpass_token):
                cls.log.error(f"{render_filename}: {renderpass_token}")
                cls.log.error(
                    f"Filename for {renderpass} should "
                    f"end with {renderpass}: {render_filename}"
                )
                invalid.append((f"Invalid {renderpass}",
                                render_filename))
        return invalid

    @classmethod
    def get_invalid_image_format(cls, instance, ext):
        """Function to check if the image format of the render outputs
        aligns with that in the setting.

        Args:
            instance (pyblish.api.Instance): instance
            ext (str): image extension

        Returns:
            list: list of files with invalid image format
        """
        invalid = []
        settings = instance.context.data["project_settings"].get("max")
        image_format = settings["RenderSettings"]["image_format"]
        ext = ext.lstrip(".")
        if ext != image_format:
            msg = (
                f"Invalid image format {ext} for render outputs.\n"
                f"Should be: {image_format}")
            cls.log.error(msg)
            invalid.append((msg, ext))
        return invalid

    @classmethod
    def get_invalid_vray_settings(cls, instance, renderer, extension, setting):
        """Function to get invalid V-Ray filenames from render outputs.

        Args:
            instance (pyblish.api.Instance): instance
        Returns:
            list: invalid filenames
        """
        invalid = []

        renderer_class = get_current_renderer()
        if "GPU" in renderer:
            vr_settings = renderer_class.V_Ray_settings
        else:
            vr_settings = renderer_class

        multipass_enabled = get_multipass_setting(renderer, setting)
        if multipass_enabled != vr_settings.output_splitgbuffer:
            invalid.append((
                "Invalid V-Ray multipass setting",
                f"Expected: {multipass_enabled}, "
                f"Found: {vr_settings.output_splitgbuffer}"
            ))
        if extension == "exr":
            vr_output_filename = vr_settings.output_rawfilename
            vr_output_path = Path(vr_output_filename)
            vr_output_fname = vr_output_path.name
            vr_name, ext = os.path.splitext(vr_output_fname)
            invalid_filenames = cls.get_invalid_filenames(
                instance, vr_name, ext)
            invalid.extend(invalid_filenames)

        if multipass_enabled:
            vr_split_filename = vr_settings.output_splitfilename
            vr_split_path = Path(vr_split_filename)
            vr_split_fname = vr_split_path.name
            vr_split_name, ext = os.path.splitext(vr_split_fname)
            invalid_filenames = cls.get_invalid_filenames(
                instance, vr_split_name, ext)
            invalid.extend(invalid_filenames)
        else:
            cls.log.debug("V-Ray multipass is not enabled, "
                          "skipping split gbuffer validation.")

        return invalid

    @classmethod
    def get_invalid_local_render_output(cls, instance):
        """Function to check if the local render output folder and filename
        are correct.

        Args:
            instance (pyblish.api.Instance): instance
        """
        invalid = []
        file = rt.maxFileName
        workfile_name, ext = os.path.splitext(file)
        # TODO: Remove this check once render output uses the $scene token template. See issue #123.
        if workfile_name not in rt.rendOutputFilename:
            cls.log.error(
                "Render output folder must include"
                f" the max scene name {workfile_name} "
            )
            invalid_folder_name = os.path.dirname(
                rt.rendOutputFilename).replace(
                    "\\", "/").split("/")[-1]
            invalid.append(("Invalid Local Render Output Folder",
                            invalid_folder_name))
        return invalid

    @classmethod
    def repair(cls, instance):
        container = instance.data.get("instance_node")
        # TODO: need to rename the function of render_output
        RenderSettings().render_output(container)
        cls.log.debug("Finished repairing the render output "
                      "folder and filenames.")

get_invalid(instance) classmethod

Function to get invalid beauty render outputs and render elements.

  1. Check Render Output Folder matches the name of the current Max Scene, e.g. The name of the current Max scene: John_Doe.max The expected render output directory: {root[work]}/{project[name]}/{hierarchy}/{asset}/ work/{task[name]}/render/3dsmax/John_Doe/

  2. Check image extension(s) of the render output(s) matches the image format in OP/AYON setting, e.g. The current image format in settings: png The expected render outputs: John_Doe.png

  3. Check filename of render element ends with the name of render element from the 3dsMax Render Element Manager. e.g. The name of render element: RsCryptomatte The expected filename: {InstanceName}_RsCryptomatte.png

Parameters:

Name Type Description Default
instance Instance

instance

required
workfile_name str

filename of the Max scene

required

Returns:

Name Type Description
list

list of invalid filename which doesn't match with the project name

Source code in client/ayon_max/plugins/publish/validate_renderpasses.py
 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
@classmethod
def get_invalid(cls, instance):
    """Function to get invalid beauty render outputs and
    render elements.

    1. Check Render Output Folder matches the name of
       the current Max Scene, e.g.
         The name of the current Max scene:
           John_Doe.max
         The expected render output directory:
           {root[work]}/{project[name]}/{hierarchy}/{asset}/
           work/{task[name]}/render/3dsmax/John_Doe/

    2. Check image extension(s) of the render output(s)
       matches the image format in OP/AYON setting, e.g.
           The current image format in settings: png
           The expected render outputs: John_Doe.png

    3. Check filename of render element ends with the name of
       render element from the 3dsMax Render Element Manager.
       e.g. The name of render element: RsCryptomatte
        The expected filename: {InstanceName}_RsCryptomatte.png

    Args:
        instance (pyblish.api.Instance): instance
        workfile_name (str): filename of the Max scene

    Returns:
        list: list of invalid filename which doesn't match
            with the project name
    """
    invalid = []
    project_settings = instance.context.data["project_settings"]
    default_render_folder = Path(
        get_default_render_folder(project_settings)
    )
    render_output = Path(rt.rendOutputFilename)
    render_dir = render_output.parent
    if default_render_folder != render_dir.parent:
        invalid.append(("Invalid render output folder",
                        os.path.dirname(rt.rendOutputFilename)))

    invalid_local_render_output = cls.get_invalid_local_render_output(instance)
    invalid.extend(invalid_local_render_output)
    renderer = instance.data.get("renderer")
    if not renderer:
        renderer_class = get_current_renderer()
        renderer = str(renderer_class).split(":")[0]

    beauty_fname = render_output.name
    beauty_name, ext = os.path.splitext(beauty_fname)
    invalid_filenames = cls.get_invalid_filenames(
        instance, beauty_name, ext)
    invalid.extend(invalid_filenames)
    invalid_image_format = cls.get_invalid_image_format(
        instance, ext.lstrip("."))
    invalid.extend(invalid_image_format)

    if is_supported_renderer(renderer):
        render_elem = rt.maxOps.GetCurRenderElementMgr()
        render_elem_num = render_elem.NumRenderElements()
        for i in range(render_elem_num):
            renderlayer_name = render_elem.GetRenderElement(i)
            renderpass = str(renderlayer_name).rsplit(":", 1)[-1]
            rend_file = render_elem.GetRenderElementFilename(i)
            if not rend_file:
                continue
            render_filename = os.path.basename(rend_file)
            rend_fname, ext = os.path.splitext(render_filename)
            invalid_image_format = cls.get_invalid_image_format(
                instance, ext)
            invalid_filenames = cls.get_invalid_filenames(
                instance, rend_fname, ext, renderpass=renderpass,
                render_filename=render_filename)
            invalid.extend(invalid_filenames)
            invalid.extend(invalid_image_format)

    elif renderer == "Arnold":
        cls.log.debug(
            "Renderpass validation does not support Arnold yet,"
            " validation skipped...")
    elif renderer.startswith("V_Ray_"):
        invalid_settings = cls.get_invalid_vray_settings(
            instance, renderer, ext, project_settings)
        invalid.extend(invalid_settings)
    else:
        cls.log.debug(
            "Skipping render element validation "
            f"for renderer: {renderer}")
    return invalid

get_invalid_filenames(instance, file_name, ext, renderpass=None, render_filename=None) classmethod

Function to get invalid filenames from render outputs.

Parameters:

Name Type Description Default
instance Instance

instance

required
file_name str

name of the file

required
ext str

image extension

required
renderpass str

name of the renderpass. Defaults to None.

None
render_filename str

render filename

None

Returns:

Name Type Description
list

invalid filenames

Source code in client/ayon_max/plugins/publish/validate_renderpasses.py
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
@classmethod
def get_invalid_filenames(
    cls, instance, file_name,
    ext, renderpass=None,
    render_filename=None):
    """Function to get invalid filenames from render outputs.

    Args:
        instance (pyblish.api.Instance): instance
        file_name (str): name of the file
        ext (str): image extension
        renderpass (str, optional): name of the renderpass.
            Defaults to None.
        render_filename(str, optional): render filename

    Returns:
        list: invalid filenames
    """
    invalid = []
    if instance.name not in file_name:
        cls.log.error("The renderpass filename should contain the instance name.")
        invalid.append(("Invalid instance name",
                        file_name))
    # TODO: check on the renderpass if _camera has been included when the multi-camera options disabled.
    if renderpass is not None and render_filename is not None:
        renderpass_token = f"{renderpass}.{ext}"
        if not render_filename.endswith(renderpass_token):
            cls.log.error(f"{render_filename}: {renderpass_token}")
            cls.log.error(
                f"Filename for {renderpass} should "
                f"end with {renderpass}: {render_filename}"
            )
            invalid.append((f"Invalid {renderpass}",
                            render_filename))
    return invalid

get_invalid_image_format(instance, ext) classmethod

Function to check if the image format of the render outputs aligns with that in the setting.

Parameters:

Name Type Description Default
instance Instance

instance

required
ext str

image extension

required

Returns:

Name Type Description
list

list of files with invalid image format

Source code in client/ayon_max/plugins/publish/validate_renderpasses.py
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
@classmethod
def get_invalid_image_format(cls, instance, ext):
    """Function to check if the image format of the render outputs
    aligns with that in the setting.

    Args:
        instance (pyblish.api.Instance): instance
        ext (str): image extension

    Returns:
        list: list of files with invalid image format
    """
    invalid = []
    settings = instance.context.data["project_settings"].get("max")
    image_format = settings["RenderSettings"]["image_format"]
    ext = ext.lstrip(".")
    if ext != image_format:
        msg = (
            f"Invalid image format {ext} for render outputs.\n"
            f"Should be: {image_format}")
        cls.log.error(msg)
        invalid.append((msg, ext))
    return invalid

get_invalid_local_render_output(instance) classmethod

Function to check if the local render output folder and filename are correct.

Parameters:

Name Type Description Default
instance Instance

instance

required
Source code in client/ayon_max/plugins/publish/validate_renderpasses.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
@classmethod
def get_invalid_local_render_output(cls, instance):
    """Function to check if the local render output folder and filename
    are correct.

    Args:
        instance (pyblish.api.Instance): instance
    """
    invalid = []
    file = rt.maxFileName
    workfile_name, ext = os.path.splitext(file)
    # TODO: Remove this check once render output uses the $scene token template. See issue #123.
    if workfile_name not in rt.rendOutputFilename:
        cls.log.error(
            "Render output folder must include"
            f" the max scene name {workfile_name} "
        )
        invalid_folder_name = os.path.dirname(
            rt.rendOutputFilename).replace(
                "\\", "/").split("/")[-1]
        invalid.append(("Invalid Local Render Output Folder",
                        invalid_folder_name))
    return invalid

get_invalid_vray_settings(instance, renderer, extension, setting) classmethod

Function to get invalid V-Ray filenames from render outputs.

Parameters:

Name Type Description Default
instance Instance

instance

required

Returns: list: invalid filenames

Source code in client/ayon_max/plugins/publish/validate_renderpasses.py
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
@classmethod
def get_invalid_vray_settings(cls, instance, renderer, extension, setting):
    """Function to get invalid V-Ray filenames from render outputs.

    Args:
        instance (pyblish.api.Instance): instance
    Returns:
        list: invalid filenames
    """
    invalid = []

    renderer_class = get_current_renderer()
    if "GPU" in renderer:
        vr_settings = renderer_class.V_Ray_settings
    else:
        vr_settings = renderer_class

    multipass_enabled = get_multipass_setting(renderer, setting)
    if multipass_enabled != vr_settings.output_splitgbuffer:
        invalid.append((
            "Invalid V-Ray multipass setting",
            f"Expected: {multipass_enabled}, "
            f"Found: {vr_settings.output_splitgbuffer}"
        ))
    if extension == "exr":
        vr_output_filename = vr_settings.output_rawfilename
        vr_output_path = Path(vr_output_filename)
        vr_output_fname = vr_output_path.name
        vr_name, ext = os.path.splitext(vr_output_fname)
        invalid_filenames = cls.get_invalid_filenames(
            instance, vr_name, ext)
        invalid.extend(invalid_filenames)

    if multipass_enabled:
        vr_split_filename = vr_settings.output_splitfilename
        vr_split_path = Path(vr_split_filename)
        vr_split_fname = vr_split_path.name
        vr_split_name, ext = os.path.splitext(vr_split_fname)
        invalid_filenames = cls.get_invalid_filenames(
            instance, vr_split_name, ext)
        invalid.extend(invalid_filenames)
    else:
        cls.log.debug("V-Ray multipass is not enabled, "
                      "skipping split gbuffer validation.")

    return invalid