diff --git a/openpype/hosts/maya/api/expected_files.py b/openpype/hosts/maya/api/expected_files.py index 186b199796..c6232f6ca4 100644 --- a/openpype/hosts/maya/api/expected_files.py +++ b/openpype/hosts/maya/api/expected_files.py @@ -184,7 +184,7 @@ class AExpectedFiles: (str): sanitized camera name Example: - >>> sanizite_camera_name('test:camera_01') + >>> AExpectedFiles.sanizite_camera_name('test:camera_01') test_camera_01 """ @@ -230,7 +230,7 @@ class AExpectedFiles: if self.layer.startswith("rs_"): layer_name = self.layer[3:] - scene_data = { + return { "frameStart": int(self.get_render_attribute("startFrame")), "frameEnd": int(self.get_render_attribute("endFrame")), "frameStep": int(self.get_render_attribute("byFrameStep")), @@ -245,7 +245,6 @@ class AExpectedFiles: "filePrefix": file_prefix, "enabledAOVs": self.get_aovs(), } - return scene_data def _generate_single_file_sequence( self, layer_data, force_aov_name=None): @@ -685,8 +684,6 @@ class ExpectedFilesRedshift(AExpectedFiles): """Expected files for Redshift renderer. Attributes: - ext_mapping (list): Mapping redshift extension dropdown values - to strings. unmerged_aovs (list): Name of aovs that are not merged into resulting exr and we need them specified in expectedFiles output. @@ -695,8 +692,6 @@ class ExpectedFilesRedshift(AExpectedFiles): unmerged_aovs = ["Cryptomatte"] - ext_mapping = ["iff", "exr", "tif", "png", "tga", "jpg"] - def __init__(self, layer, render_instance): """Construtor.""" super(ExpectedFilesRedshift, self).__init__(layer, render_instance) @@ -785,12 +780,10 @@ class ExpectedFilesRedshift(AExpectedFiles): # anyway. return enabled_aovs - default_ext = self.ext_mapping[ - cmds.getAttr("redshiftOptions.imageFormat") - ] + default_ext = cmds.getAttr( + "redshiftOptions.imageFormat", asString=True) rs_aovs = cmds.ls(type="RedshiftAOV", referencedNodes=False) - # todo: find out how to detect multichannel exr for redshift for aov in rs_aovs: enabled = self.maya_is_true(cmds.getAttr("{}.enabled".format(aov))) for override in self.get_layer_overrides( diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 907f9cf781..cbca091365 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -12,7 +12,7 @@ from openpype.hosts.maya.api import ( lib, plugin ) -from openpype.api import get_system_settings +from openpype.api import (get_system_settings, get_asset) class CreateRender(plugin.Creator): @@ -104,7 +104,7 @@ class CreateRender(plugin.Creator): # namespace is not empty, so we leave it untouched pass - while(cmds.namespace(exists=namespace_name)): + while cmds.namespace(exists=namespace_name): namespace_name = "_{}{}".format(str(instance), index) index += 1 @@ -125,7 +125,7 @@ class CreateRender(plugin.Creator): cmds.sets(sets, forceElement=instance) # if no render layers are present, create default one with - # asterix selector + # asterisk selector if not layers: render_layer = self._rs.createRenderLayer('Main') collection = render_layer.createCollection("defaultCollection") @@ -137,9 +137,7 @@ class CreateRender(plugin.Creator): if renderer.startswith('renderman'): renderer = 'renderman' - cmds.setAttr(self._image_prefix_nodes[renderer], - self._image_prefixes[renderer], - type="string") + self._set_default_renderer_settings(renderer) def _create_render_settings(self): # get pools @@ -318,3 +316,86 @@ class CreateRender(plugin.Creator): False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True ) # noqa return requests.get(*args, **kwargs) + + def _set_default_renderer_settings(self, renderer): + """Set basic settings based on renderer. + + Args: + renderer (str): Renderer name. + + """ + cmds.setAttr(self._image_prefix_nodes[renderer], + self._image_prefixes[renderer], + type="string") + + asset = get_asset() + + if renderer == "arnold": + # set format to exr + cmds.setAttr( + "defaultArnoldDriver.ai_translator", "exr", type="string") + # enable animation + cmds.setAttr("defaultRenderGlobals.outFormatControl", 0) + cmds.setAttr("defaultRenderGlobals.animation", 1) + cmds.setAttr("defaultRenderGlobals.putFrameBeforeExt", 1) + cmds.setAttr("defaultRenderGlobals.extensionPadding", 4) + + # resolution + cmds.setAttr( + "defaultResolution.width", + asset["data"].get("resolutionWidth")) + cmds.setAttr( + "defaultResolution.height", + asset["data"].get("resolutionHeight")) + + if renderer == "vray": + vray_settings = cmds.ls(type="VRaySettingsNode") + if not vray_settings: + node = cmds.createNode("VRaySettingsNode") + else: + node = vray_settings[0] + + # set underscore as element separator instead of default `.` + cmds.setAttr( + "{}.fileNameRenderElementSeparator".format( + node), + "_" + ) + # set format to exr + cmds.setAttr( + "{}.imageFormatStr".format(node), 5) + + # animType + cmds.setAttr( + "{}.animType".format(node), 1) + + # resolution + cmds.setAttr( + "{}.width".format(node), + asset["data"].get("resolutionWidth")) + cmds.setAttr( + "{}.height".format(node), + asset["data"].get("resolutionHeight")) + + if renderer == "redshift": + redshift_settings = cmds.ls(type="RedshiftOptions") + if not redshift_settings: + node = cmds.createNode("RedshiftOptions") + else: + node = redshift_settings[0] + + # set exr + cmds.setAttr("{}.imageFormat".format(node), 1) + # resolution + cmds.setAttr( + "defaultResolution.width", + asset["data"].get("resolutionWidth")) + cmds.setAttr( + "defaultResolution.height", + asset["data"].get("resolutionHeight")) + + # enable animation + cmds.setAttr("defaultRenderGlobals.outFormatControl", 0) + cmds.setAttr("defaultRenderGlobals.animation", 1) + cmds.setAttr("defaultRenderGlobals.putFrameBeforeExt", 1) + cmds.setAttr("defaultRenderGlobals.extensionPadding", 4) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index ba676bee83..9aeaad7ff1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -1,8 +1,9 @@ -import os +# -*- coding: utf-8 -*- +"""Maya validator for render settings.""" import re +from collections import OrderedDict from maya import cmds, mel -import pymel.core as pm import pyblish.api import openpype.api @@ -60,6 +61,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): 'renderman': '_..' } + redshift_AOV_prefix = "/_" + # WARNING: There is bug? in renderman, translating token # to something left behind mayas default image prefix. So instead # `SceneName_v01` it translates to: @@ -120,25 +123,59 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): "doesn't have: '' or " "'' token".format(prefix)) - if len(cameras) > 1: - if not re.search(cls.R_CAMERA_TOKEN, prefix): - invalid = True - cls.log.error("Wrong image prefix [ {} ] - " - "doesn't have: '' token".format(prefix)) + if len(cameras) > 1 and not re.search(cls.R_CAMERA_TOKEN, prefix): + invalid = True + cls.log.error("Wrong image prefix [ {} ] - " + "doesn't have: '' token".format(prefix)) # renderer specific checks if renderer == "vray": - # no vray checks implemented yet - pass - elif renderer == "redshift": + vray_settings = cmds.ls(type="VRaySettingsNode") + if not vray_settings: + node = cmds.createNode("VRaySettingsNode") + else: + node = vray_settings[0] + + if cmds.getAttr( + "{}.fileNameRenderElementSeparator".format(node)) != "_": + invalid = False + cls.log.error("AOV separator is not set correctly.") + + if renderer == "redshift": if re.search(cls.R_AOV_TOKEN, prefix): invalid = True - cls.log.error("Do not use AOV token [ {} ] - " - "Redshift automatically append AOV name and " - "it doesn't make much sense with " - "Multipart EXR".format(prefix)) + cls.log.error(("Do not use AOV token [ {} ] - " + "Redshift is using image prefixes per AOV so " + "it doesn't make much sense using it in global" + "image prefix").format(prefix)) + # get redshift AOVs + rs_aovs = cmds.ls(type="RedshiftAOV", referencedNodes=False) + for aov in rs_aovs: + aov_prefix = cmds.getAttr("{}.filePrefix".format(aov)) + # check their image prefix + if aov_prefix != cls.redshift_AOV_prefix: + cls.log.error(("AOV ({}) image prefix is not set " + "correctly {} != {}").format( + cmds.getAttr("{}.name".format(aov)), + cmds.getAttr("{}.filePrefix".format(aov)), + aov_prefix + )) + invalid = True + # get aov format + aov_ext = cmds.getAttr( + "{}.fileFormat".format(aov), asString=True) - elif renderer == "renderman": + default_ext = cmds.getAttr( + "redshiftOptions.imageFormat", asString=True) + + if default_ext != aov_ext: + cls.log.error(("AOV file format is not the same " + "as the one set globally " + "{} != {}").format(default_ext, + aov_ext)) + invalid = True + + if renderer == "renderman": file_prefix = cmds.getAttr("rmanGlobals.imageFileFormat") dir_prefix = cmds.getAttr("rmanGlobals.imageOutputDir") @@ -151,7 +188,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error("Wrong directory prefix [ {} ]".format( dir_prefix)) - else: + if renderer == "arnold": multipart = cmds.getAttr("defaultArnoldDriver.mergeAOVs") if multipart: if re.search(cls.R_AOV_TOKEN, prefix): @@ -177,6 +214,43 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cls.log.error("Expecting padding of {} ( {} )".format( cls.DEFAULT_PADDING, "0" * cls.DEFAULT_PADDING)) + # load validation definitions from settings + validation_settings = ( + instance.context.data["project_settings"]["maya"]["publish"]["ValidateRenderSettings"].get( # noqa: E501 + "{}_render_attributes".format(renderer)) + ) + + # go through definitions and test if such node.attribute exists. + # if so, compare its value from the one required. + for attr, value in OrderedDict(validation_settings).items(): + # first get node of that type + cls.log.debug("{}: {}".format(attr, value)) + node_type = attr.split(".")[0] + attribute_name = ".".join(attr.split(".")[1:]) + nodes = cmds.ls(type=node_type) + + if not isinstance(nodes, list): + cls.log.warning("No nodes of '{}' found.".format(node_type)) + continue + + for node in nodes: + try: + render_value = cmds.getAttr( + "{}.{}".format(node, attribute_name)) + except RuntimeError: + invalid = True + cls.log.error( + "Cannot get value of {}.{}".format( + node, attribute_name)) + else: + if value != render_value: + invalid = True + cls.log.error( + ("Invalid value {} set on {}.{}. " + "Expecting {}").format( + render_value, node, attribute_name, value) + ) + return invalid @classmethod @@ -210,3 +284,29 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): cmds.setAttr("rmanGlobals.imageOutputDir", cls.RendermanDirPrefix, type="string") + + if renderer == "vray": + vray_settings = cmds.ls(type="VRaySettingsNode") + if not vray_settings: + node = cmds.createNode("VRaySettingsNode") + else: + node = vray_settings[0] + + cmds.setAttr( + "{}.fileNameRenderElementSeparator".format( + node), + "_" + ) + + if renderer == "redshift": + # get redshift AOVs + rs_aovs = cmds.ls(type="RedshiftAOV", referencedNodes=False) + for aov in rs_aovs: + # fix AOV prefixes + cmds.setAttr( + "{}.filePrefix".format(aov), cls.redshift_AOV_prefix) + # fix AOV file format + default_ext = cmds.getAttr( + "redshiftOptions.imageFormat", asString=True) + cmds.setAttr( + "{}.fileFormat".format(aov), default_ext) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8600e49518..779b8bb3f3 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -135,6 +135,12 @@ "enabled": false, "attributes": {} }, + "ValidateRenderSettings": { + "arnold_render_attributes": [], + "vray_render_attributes": [], + "redshift_render_attributes": [], + "renderman_render_attributes": [] + }, "ValidateModelName": { "enabled": false, "material_file": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json index 95b02a7936..4cabf5bb74 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json @@ -72,6 +72,56 @@ } ] }, + + { + "type": "dict", + "collapsible": true, + "key": "ValidateRenderSettings", + "label": "ValidateRenderSettings", + "children": [ + { + "type": "dict-modifiable", + "store_as_list": true, + "key": "arnold_render_attributes", + "label": "Arnold Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + }, + { + "type": "dict-modifiable", + "store_as_list": true, + "key": "vray_render_attributes", + "label": "Vray Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + }, + { + "type": "dict-modifiable", + "store_as_list": true, + "key": "redshift_render_attributes", + "label": "Redshift Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + }, + { + "type": "dict-modifiable", + "store_as_list": true, + "key": "renderman_render_attributes", + "label": "Renderman Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + } + ] + }, + { "type": "collapsible-wrap", "label": "Model", diff --git a/website/docs/admin_hosts_maya.md b/website/docs/admin_hosts_maya.md new file mode 100644 index 0000000000..83c4121be9 --- /dev/null +++ b/website/docs/admin_hosts_maya.md @@ -0,0 +1,52 @@ +--- +id: admin_hosts_maya +title: Maya +sidebar_label: Maya +--- + +## Maya + +### Publish Plugins + +#### Render Settings Validator (`ValidateRenderSettings`) + +Render Settings Validator is here to make sure artists will submit renders +we correct settings. Some of these settings are needed by OpenPype but some +can be defined by TD using [OpenPype Settings UI](admin_settings). + +OpenPype enforced settings include: + +- animation must be enabled in output +- render prefix must start with `maya/` to make sure renders are in +correct directory +- there must be `` or its equivalent in different renderers in +file prefix +- if multiple cameras are to be rendered, `` token must be in file prefix + +For **Vray**: +- AOV separator must be set to `_` (underscore) + +For **Redshift**: +- all AOVs must follow `/_` image file prefix +- AOV image format must be same as the one set in Output settings + +For **Renderman**: +- both image and directory prefixes must comply to `_..` and `/renders/maya//` respectively + +For **Arnold**: +- there shouldn't be `` token when merge AOVs option is turned on + + +Additional check can be added via Settings - **Project Settings > Maya > Publish plugin > ValidateRenderSettings**. +You can add as many options as you want for every supported renderer. In first field put node type and attribute +and in the second required value. + +![Settings example](assets/maya-admin_render_settings_validator.png) + +In this example we've put `aiOptions.AA_samples` in first one and `6` to second to enforce +Arnolds Camera (AA) samples to 6. + +Note that `aiOptions` is not the name of node but rather its type. For renderers there is usually +just one instance of this node type but if that is not so, validator will go through all its +instances and check the value there. Node type for **VRay** settings is `VRaySettingsNode`, for **Renderman** +it is `rmanGlobals`, for **Redshift** it is `RedshiftOptions`. \ No newline at end of file diff --git a/website/docs/assets/maya-admin_render_settings_validator.png b/website/docs/assets/maya-admin_render_settings_validator.png new file mode 100644 index 0000000000..8687b538b1 Binary files /dev/null and b/website/docs/assets/maya-admin_render_settings_validator.png differ diff --git a/website/sidebars.js b/website/sidebars.js index 78a1879c30..c9edf5e3b7 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -84,6 +84,7 @@ module.exports = { label: "Integrations", items: [ "admin_hosts_blender", + "admin_hosts_maya", "admin_hosts_resolve", "admin_hosts_harmony", "admin_hosts_aftereffects"