From 46dc52c4ea38ca0e9b85b927c06d5911e4c8e901 Mon Sep 17 00:00:00 2001 From: antirotor Date: Fri, 7 May 2021 16:07:56 +0000 Subject: [PATCH 1/7] Create draft PR for #1159 From 16f3d9c38987ac4b53c884d6550e9be7b04cef82 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 7 May 2021 18:34:17 +0200 Subject: [PATCH 2/7] create sane render default and validation wip --- .../maya/plugins/create/create_render.py | 93 +++++++++++++++++-- .../publish/validate_rendersettings.py | 39 ++++++-- 2 files changed, 117 insertions(+), 15 deletions(-) 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..afaae9cd89 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -1,8 +1,8 @@ -import os +# -*- coding: utf-8 -*- +"""Maya validator for render settings.""" import re from maya import cmds, mel -import pymel.core as pm import pyblish.api import openpype.api @@ -120,16 +120,24 @@ 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 + 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.") + elif renderer == "redshift": if re.search(cls.R_AOV_TOKEN, prefix): invalid = True @@ -210,3 +218,16 @@ 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), + "_" + ) \ No newline at end of file From 28f37046b141d7b13e9cc40a3ae9c9c99a0f5fca Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 10 May 2021 18:43:41 +0200 Subject: [PATCH 3/7] handle redshift formats and separator --- openpype/hosts/maya/api/expected_files.py | 15 ++--- .../publish/validate_rendersettings.py | 59 ++++++++++++++++--- 2 files changed, 54 insertions(+), 20 deletions(-) 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/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index afaae9cd89..c2ed1eeaf0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -60,6 +60,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: @@ -138,15 +140,41 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): invalid = False cls.log.error("AOV separator is not set correctly.") - elif renderer == "redshift": + 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") @@ -159,7 +187,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): @@ -225,9 +253,22 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): node = cmds.createNode("VRaySettingsNode") else: node = vray_settings[0] - + cmds.setAttr( "{}.fileNameRenderElementSeparator".format( node), "_" - ) \ No newline at end of file + ) + + 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) From 7b31f7f00088f2be105f70a6a8415dbfa34c0c92 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 10 May 2021 22:37:34 +0200 Subject: [PATCH 4/7] add render settings to settings :grin: --- .../schemas/schema_maya_publish.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) 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..b737dcda70 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,52 @@ } ] }, + + { + "type": "dict", + "collapsible": true, + "key": "ValidateRenderSettings", + "label": "ValidateRenderSettings", + "children": [ + { + "type": "dict-modifiable", + "key": "arnold_render_attributes", + "label": "Arnold Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + }, + { + "type": "dict-modifiable", + "key": "vray_render_attributes", + "label": "Vray Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + }, + { + "type": "dict-modifiable", + "key": "redshift_render_attributes", + "label": "Redshift Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + }, + { + "type": "dict-modifiable", + "key": "renderman_render_attributes", + "label": "Renderman Render Attributes", + "use_label_wrap": true, + "object_type": { + "type": "text" + } + } + ] + }, + { "type": "collapsible-wrap", "label": "Model", From 5f79df6dd601d6d14fc199034700df770e96f6cc Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 12 May 2021 17:52:14 +0200 Subject: [PATCH 5/7] validate list of node types --- .../publish/validate_rendersettings.py | 39 +++++++++++++++++++ .../defaults/project_settings/maya.json | 6 +++ .../schemas/schema_maya_publish.json | 4 ++ 3 files changed, 49 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index c2ed1eeaf0..308612e42e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Maya validator for render settings.""" import re +from collections import OrderedDict from maya import cmds, mel @@ -213,6 +214,44 @@ 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( + "{}_render_attributes".format(renderer)) + ) + from pprint import pprint + pprint(validation_settings) + # 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 as e: + 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 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 b737dcda70..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 @@ -81,6 +81,7 @@ "children": [ { "type": "dict-modifiable", + "store_as_list": true, "key": "arnold_render_attributes", "label": "Arnold Render Attributes", "use_label_wrap": true, @@ -90,6 +91,7 @@ }, { "type": "dict-modifiable", + "store_as_list": true, "key": "vray_render_attributes", "label": "Vray Render Attributes", "use_label_wrap": true, @@ -99,6 +101,7 @@ }, { "type": "dict-modifiable", + "store_as_list": true, "key": "redshift_render_attributes", "label": "Redshift Render Attributes", "use_label_wrap": true, @@ -108,6 +111,7 @@ }, { "type": "dict-modifiable", + "store_as_list": true, "key": "renderman_render_attributes", "label": "Renderman Render Attributes", "use_label_wrap": true, From b835282ad7254444ebf586f9f606efa4ace5ae35 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 12 May 2021 17:55:25 +0200 Subject: [PATCH 6/7] hound fixes --- .../hosts/maya/plugins/publish/validate_rendersettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index 308612e42e..db835da29f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -216,7 +216,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # load validation definitions from settings validation_settings = ( - instance.context.data["project_settings"]["maya"]["publish"]["ValidateRenderSettings"].get( + instance.context.data["project_settings"]["maya"]["publish"]["ValidateRenderSettings"].get( # noqa: E501 "{}_render_attributes".format(renderer)) ) from pprint import pprint @@ -238,7 +238,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): try: render_value = cmds.getAttr( "{}.{}".format(node, attribute_name)) - except RuntimeError as e: + except RuntimeError: invalid = True cls.log.error( "Cannot get value of {}.{}".format( From 7d792899bc4b0d00e89b987614eeab653a607630 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Wed, 12 May 2021 18:38:22 +0200 Subject: [PATCH 7/7] add documentation for validator --- .../publish/validate_rendersettings.py | 3 +- website/docs/admin_hosts_maya.md | 52 ++++++++++++++++++ .../maya-admin_render_settings_validator.png | Bin 0 -> 11220 bytes website/sidebars.js | 3 +- 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 website/docs/admin_hosts_maya.md create mode 100644 website/docs/assets/maya-admin_render_settings_validator.png diff --git a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py index db835da29f..9aeaad7ff1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rendersettings.py +++ b/openpype/hosts/maya/plugins/publish/validate_rendersettings.py @@ -219,8 +219,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): instance.context.data["project_settings"]["maya"]["publish"]["ValidateRenderSettings"].get( # noqa: E501 "{}_render_attributes".format(renderer)) ) - from pprint import pprint - pprint(validation_settings) + # 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(): 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 0000000000000000000000000000000000000000..8687b538b124c6857534d9033a46c70deb875fde GIT binary patch literal 11220 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