Merge remote-tracking branch 'origin/pr/2643' into Enhancement/OP-2040_Maya_Render_creator-should-have-configurable-options-

This commit is contained in:
Allan Ihsan 2022-04-20 23:19:14 +03:00
commit 2a76f1231d
5 changed files with 257 additions and 148 deletions

View file

@ -80,6 +80,13 @@ IMAGE_PREFIXES = {
}
def has_tokens(string, tokens):
"""Return whether any of tokens is in input string (case-insensitive)"""
pattern = "({})".format("|".join(re.escape(token) for token in tokens))
match = re.search(pattern, string, re.IGNORECASE)
return bool(match)
@attr.s
class LayerMetadata(object):
"""Data class for Render Layer metadata."""
@ -97,6 +104,12 @@ class LayerMetadata(object):
# Render Products
products = attr.ib(init=False, default=attr.Factory(list))
# The AOV separator token. Note that not all renderers define an explicit
# render separator but allow to put the AOV/RenderPass token anywhere in
# the file path prefix. For those renderers we'll fall back to whatever
# is between the last occurrences of <RenderLayer> and <RenderPass> tokens.
aov_separator = attr.ib(default="_")
@attr.s
class RenderProduct(object):
@ -180,7 +193,6 @@ class ARenderProducts:
self.layer = layer
self.render_instance = render_instance
self.multipart = False
self.aov_separator = render_instance.data.get("aovSeparator", "_")
# Initialize
self.layer_data = self._get_layer_data()
@ -316,6 +328,31 @@ class ARenderProducts:
# defaultRenderLayer renders as masterLayer
layer_name = "masterLayer"
# AOV separator - default behavior extracts the part between
# last occurences of <RenderLayer> and <RenderPass>
# todo: This code also triggers for V-Ray which overrides it explicitly
# so this code will invalidly debug log it couldn't extract the
# aov separator even though it does set it in RenderProductsVray
layer_tokens = ["<renderlayer>", "<layer>"]
aov_tokens = ["<aov>", "<renderpass>"]
def match_last(tokens, text):
"""regex match the last occurence from a list of tokens"""
pattern = "(?:.*)({})".format("|".join(tokens))
return re.search(pattern, text, re.IGNORECASE)
layer_match = match_last(layer_tokens, file_prefix)
aov_match = match_last(aov_tokens, file_prefix)
kwargs = {}
if layer_match and aov_match:
matches = sorted((layer_match, aov_match),
key=lambda match: match.end(1))
separator = file_prefix[matches[0].end(1):matches[1].start(1)]
kwargs["aov_separator"] = separator
else:
log.debug("Couldn't extract aov separator from "
"file prefix: {}".format(file_prefix))
# todo: Support Custom Frames sequences 0,5-10,100-120
# Deadline allows submitting renders with a custom frame list
# to support those cases we might want to allow 'custom frames'
@ -332,7 +369,8 @@ class ARenderProducts:
layerName=layer_name,
renderer=self.renderer,
defaultExt=self._get_attr("defaultRenderGlobals.imfPluginKey"),
filePrefix=file_prefix
filePrefix=file_prefix,
**kwargs
)
def _generate_file_sequence(
@ -677,9 +715,17 @@ class RenderProductsVray(ARenderProducts):
"""
prefix = super(RenderProductsVray, self).get_renderer_prefix()
prefix = "{}{}<aov>".format(prefix, self.aov_separator)
aov_separator = self._get_aov_separator()
prefix = "{}{}<aov>".format(prefix, aov_separator)
return prefix
def _get_aov_separator(self):
# type: () -> str
"""Return the V-Ray AOV/Render Elements separator"""
return self._get_attr(
"vraySettings.fileNameRenderElementSeparator"
)
def _get_layer_data(self):
# type: () -> LayerMetadata
"""Override to get vray specific extension."""
@ -691,6 +737,8 @@ class RenderProductsVray(ARenderProducts):
layer_data.defaultExt = default_ext
layer_data.padding = self._get_attr("vraySettings.fileNamePadding")
layer_data.aov_separator = self._get_aov_separator()
return layer_data
def get_render_products(self):

View file

@ -0,0 +1,155 @@
from maya import cmds
from openpype.api import (
get_project_settings,
get_asset)
from avalon.api import Session
from avalon.api import CreatorError
class RenderSettings(object):
_image_prefix_nodes = {
'mentalray': 'defaultRenderGlobals.imageFilePrefix',
'vray': 'vraySettings.fileNamePrefix',
'arnold': 'defaultRenderGlobals.imageFilePrefix',
'renderman': 'defaultRenderGlobals.imageFilePrefix',
'redshift': 'defaultRenderGlobals.imageFilePrefix'
}
_image_prefixes = {
'mentalray': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
'vray': 'maya/<scene>/<Layer>/<Layer>',
'arnold': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
'renderman': 'maya/<Scene>/<layer>/<layer>{aov_separator}<aov>',
'redshift': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>' # noqa
}
_aov_chars = {
"dot": ".",
"dash": "-",
"underscore": "_"
}
@classmethod
def get_image_prefix_attr(cls, renderer):
return cls._image_prefix_nodes[renderer]
def __init__(self, project_settings):
self._project_settings = project_settings
@staticmethod
def apply_defaults(renderer=None, project_settings=None):
if renderer is None:
renderer = cmds.getAttr(
'defaultRenderGlobals.currentRenderer').lower()
# handle various renderman names
if renderer.startswith('renderman'):
renderer = 'renderman'
if project_settings is None:
project_settings = get_project_settings(Session["AVALON_PROJECT"])
render_settings = RenderSettings(project_settings)
render_settings.set_default_renderer_settings(renderer)
def set_default_renderer_settings(self, renderer):
"""Set basic settings based on renderer.
Args:
renderer (str): Renderer name.
"""
# project_settings/maya/create/CreateRender/aov_separator
try:
aov_separator = self._aov_chars[(
self._project_settings["maya"]
["create"]
["CreateRender"]
["aov_separator"]
)]
except KeyError:
aov_separator = "_"
prefix = self._image_prefixes[renderer]
prefix = prefix.replace("{aov_separator}", aov_separator)
cmds.setAttr(self._image_prefix_nodes[renderer],
prefix,
type="string")
asset = get_asset()
width = asset["data"].get("resolutionWidth")
height = asset["data"].get("resolutionHeight")
if renderer == "arnold":
# set format to exr
cmds.setAttr(
"defaultArnoldDriver.ai_translator", "exr", type="string")
self._set_global_output_settings()
# resolution
cmds.setAttr("defaultResolution.width", width)
cmds.setAttr("defaultResolution.height", height)
if renderer == "vray":
self._set_vray_settings(aov_separator, width, height)
if renderer == "redshift":
# set format to exr
cmds.setAttr("RedshiftOptions.imageFormat", 1)
# resolution
cmds.setAttr("defaultResolution.width", width)
cmds.setAttr("defaultResolution.height", height)
self._set_global_output_settings()
def _set_vray_settings(self, aov_separator, width, height):
# type: (str, int, int) -> None
"""Sets important settings for Vray."""
settings = cmds.ls(type="VRaySettingsNode")
node = settings[0] if settings else cmds.createNode("VRaySettingsNode")
# Set aov separator
# First we need to explicitly set the UI items in Render Settings
# because that is also what V-Ray updates to when that Render Settings
# UI did initialize before and refreshes again.
MENU = "vrayRenderElementSeparator"
if cmds.optionMenuGrp(MENU, query=True, exists=True):
items = cmds.optionMenuGrp(MENU, query=True, ill=True)
separators = [cmds.menuItem(i, query=True, label=True) for i in items] # noqa: E501
try:
sep_idx = separators.index(aov_separator)
except ValueError:
raise CreatorError(
"AOV character {} not in {}".format(
aov_separator, separators))
cmds.optionMenuGrp(MENU, edit=True, select=sep_idx + 1)
# Set the render element attribute as string. This is also what V-Ray
# sets whenever the `vrayRenderElementSeparator` menu items switch
cmds.setAttr(
"{}.fileNameRenderElementSeparator".format(node),
aov_separator,
type="string"
)
# set format to exr
cmds.setAttr("{}.imageFormatStr".format(node), "exr", type="string")
# animType
cmds.setAttr("{}.animType".format(node), 1)
# resolution
cmds.setAttr("{}.width".format(node), width)
cmds.setAttr("{}.height".format(node), height)
@staticmethod
def _set_global_output_settings():
# enable animation
cmds.setAttr("defaultRenderGlobals.outFormatControl", 0)
cmds.setAttr("defaultRenderGlobals.animation", 1)
cmds.setAttr("defaultRenderGlobals.putFrameBeforeExt", 1)
cmds.setAttr("defaultRenderGlobals.extensionPadding", 4)

View file

@ -1,29 +1,31 @@
# -*- coding: utf-8 -*-
"""Create ``Render`` instance in Maya."""
import os
import json
import os
import sys
import appdirs
import requests
import six
import sys
from maya import cmds
import maya.app.renderSetup.model.renderSetup as renderSetup
from maya.app.renderSetup.model import renderSetup
from openpype.hosts.maya.api import (
lib,
plugin
)
from avalon.api import Session
from openpype.api import (
get_system_settings,
get_project_settings,
get_asset)
get_project_settings
)
from openpype.hosts.maya.api import (
lib,
plugin,
render_settings
)
from openpype.modules import ModulesManager
from openpype.pipeline import CreatorError
from avalon.api import Session
class CreateRender(plugin.Creator):
"""Create *render* instance.
@ -69,31 +71,6 @@ class CreateRender(plugin.Creator):
_user = None
_password = None
# renderSetup instance
_rs = None
_image_prefix_nodes = {
'mentalray': 'defaultRenderGlobals.imageFilePrefix',
'vray': 'vraySettings.fileNamePrefix',
'arnold': 'defaultRenderGlobals.imageFilePrefix',
'renderman': 'defaultRenderGlobals.imageFilePrefix',
'redshift': 'defaultRenderGlobals.imageFilePrefix'
}
_image_prefixes = {
'mentalray': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
'vray': 'maya/<scene>/<Layer>/<Layer>',
'arnold': 'maya/<Scene>/<RenderLayer>/<RenderLayer>{aov_separator}<RenderPass>', # noqa
'renderman': 'maya/<Scene>/<layer>/<layer>{aov_separator}<aov>',
'redshift': 'maya/<Scene>/<RenderLayer>/<RenderLayer>' # noqa
}
_aov_chars = {
"dot": ".",
"dash": "-",
"underscore": "_"
}
_project_settings = None
def __init__(self, *args, **kwargs):
@ -106,17 +83,6 @@ class CreateRender(plugin.Creator):
self._project_settings = get_project_settings(
Session["AVALON_PROJECT"])
# project_settings/maya/create/CreateRender/aov_separator
try:
self.aov_separator = self._aov_chars[(
self._project_settings["maya"]
["create"]
["CreateRender"]
["aov_separator"]
)]
except KeyError:
self.aov_separator = "_"
try:
default_servers = deadline_settings["deadline_urls"]
project_servers = (
@ -173,13 +139,13 @@ class CreateRender(plugin.Creator):
])
cmds.setAttr("{}.machineList".format(self.instance), lock=True)
self._rs = renderSetup.instance()
layers = self._rs.getRenderLayers()
rs = renderSetup.instance()
layers = rs.getRenderLayers()
if use_selection:
print(">>> processing existing layers")
self.log.info("Processing existing layers")
sets = []
for layer in layers:
print(" - creating set for {}:{}".format(
self.log.info(" - creating set for {}:{}".format(
namespace, layer.name()))
render_set = cmds.sets(
n="{}:{}".format(namespace, layer.name()))
@ -189,17 +155,12 @@ class CreateRender(plugin.Creator):
# if no render layers are present, create default one with
# asterisk selector
if not layers:
render_layer = self._rs.createRenderLayer('Main')
render_layer = rs.createRenderLayer('Main')
collection = render_layer.createCollection("defaultCollection")
collection.getSelector().setPattern('*')
renderer = cmds.getAttr(
'defaultRenderGlobals.currentRenderer').lower()
# handle various renderman names
if renderer.startswith('renderman'):
renderer = 'renderman'
self._set_default_renderer_settings(renderer)
self.log.info("Applying default render settings..")
render_settings.RenderSettings.apply_defaults()
return self.instance
def _deadline_webservice_changed(self):

View file

@ -72,7 +72,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
def process(self, context):
"""Entry point to collector."""
render_instance = None
deadline_url = None
for instance in context:
if "rendering" in instance.data["families"]:
@ -96,23 +95,12 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
asset = api.Session["AVALON_ASSET"]
workspace = context.data["workspaceDir"]
deadline_settings = (
context.data
["system_settings"]
["modules"]
["deadline"]
)
if deadline_settings["enabled"]:
deadline_url = render_instance.data.get("deadlineUrl")
self._rs = renderSetup.instance()
current_layer = self._rs.getVisibleRenderLayer()
# Retrieve render setup layers
rs = renderSetup.instance()
maya_render_layers = {
layer.name(): layer for layer in self._rs.getRenderLayers()
layer.name(): layer for layer in rs.getRenderLayers()
}
self.maya_layers = maya_render_layers
for layer in collected_render_layers:
try:
if layer.startswith("LAYER_"):
@ -147,49 +135,34 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
self.log.warning(msg)
continue
# test if there are sets (subsets) to attach render to
# detect if there are sets (subsets) to attach render to
sets = cmds.sets(layer, query=True) or []
attach_to = []
if sets:
for s in sets:
if "family" not in cmds.listAttr(s):
continue
for s in sets:
if not cmds.attributeQuery("family", node=s, exists=True):
continue
attach_to.append(
{
"version": None, # we need integrator for that
"subset": s,
"family": cmds.getAttr("{}.family".format(s)),
}
)
self.log.info(" -> attach render to: {}".format(s))
attach_to.append(
{
"version": None, # we need integrator for that
"subset": s,
"family": cmds.getAttr("{}.family".format(s)),
}
)
self.log.info(" -> attach render to: {}".format(s))
layer_name = "rs_{}".format(expected_layer_name)
# collect all frames we are expecting to be rendered
renderer = cmds.getAttr(
"defaultRenderGlobals.currentRenderer"
).lower()
renderer = self.get_render_attribute("currentRenderer",
layer=layer_name)
# handle various renderman names
if renderer.startswith("renderman"):
renderer = "renderman"
try:
aov_separator = self._aov_chars[(
context.data["project_settings"]
["create"]
["CreateRender"]
["aov_separator"]
)]
except KeyError:
aov_separator = "_"
render_instance.data["aovSeparator"] = aov_separator
# return all expected files for all cameras and aovs in given
# frame range
layer_render_products = get_layer_render_products(
layer_name, render_instance)
layer_render_products = get_layer_render_products(layer_name)
render_products = layer_render_products.layer_data.products
assert render_products, "no render products generated"
exp_files = []
@ -266,8 +239,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
frame_start_handle = frame_start_render
frame_end_handle = frame_end_render
full_exp_files.append(aov_dict)
# find common path to store metadata
# so if image prefix is branching to many directories
# metadata file will be located in top-most common
@ -296,16 +267,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
self.log.info("collecting layer: {}".format(layer_name))
# Get layer specific settings, might be overrides
try:
aov_separator = self._aov_chars[(
context.data["project_settings"]
["create"]
["CreateRender"]
["aov_separator"]
)]
except KeyError:
aov_separator = "_"
data = {
"subset": expected_layer_name,
"attachTo": attach_to,
@ -323,8 +284,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
"byFrameStep": int(
self.get_render_attribute("byFrameStep",
layer=layer_name)),
"renderer": self.get_render_attribute("currentRenderer",
layer=layer_name),
"renderer": renderer,
# instance subset
"family": "renderlayer",
"families": ["renderlayer"],
@ -351,8 +311,12 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
"aovSeparator": aov_separator
}
if deadline_url:
data["deadlineUrl"] = deadline_url
# Collect Deadline url if Deadline module is enabled
deadline_settings = (
context.data["system_settings"]["modules"]["deadline"]
)
if deadline_settings["enabled"]:
data["deadlineUrl"] = render_instance.data.get("deadlineUrl")
if self.sync_workfile_version:
data["version"] = context.data["version"]
@ -362,16 +326,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
instance.data["version"] = context.data["version"]
# Apply each user defined attribute as data
for attr in cmds.listAttr(layer, userDefined=True) or list():
try:
value = cmds.getAttr("{}.{}".format(layer, attr))
except Exception:
# Some attributes cannot be read directly,
# such as mesh and color attributes. These
# are considered non-essential to this
# particular publishing pipeline.
value = None
for attr, value in avalon.maya.read(layer).items():
data[attr] = value
# handle standalone renderers
@ -475,10 +430,6 @@ class CollectMayaRender(pyblish.api.ContextPlugin):
return pool_a, pool_b
def _get_overrides(self, layer):
rset = self.maya_layers[layer].renderSettingsCollectionInstance()
return rset.getOverrides()
@staticmethod
def get_render_attribute(attr, layer):
"""Get attribute from render options.

View file

@ -1,19 +1,11 @@
import re
import pyblish.api
import openpype.api
import openpype.hosts.maya.api.action
from maya import cmds
ImagePrefixes = {
'mentalray': 'defaultRenderGlobals.imageFilePrefix',
'vray': 'vraySettings.fileNamePrefix',
'arnold': 'defaultRenderGlobals.imageFilePrefix',
'renderman': 'defaultRenderGlobals.imageFilePrefix',
'redshift': 'defaultRenderGlobals.imageFilePrefix'
}
import openpype.api
import openpype.hosts.maya.api.action
from openpype.hosts.maya.api.render_settings import RenderSettings
class ValidateRenderSingleCamera(pyblish.api.InstancePlugin):
@ -46,7 +38,9 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin):
# handle various renderman names
if renderer.startswith('renderman'):
renderer = 'renderman'
file_prefix = cmds.getAttr(ImagePrefixes[renderer])
attr = RenderSettings.get_image_prefix_attr(renderer)
file_prefix = cmds.getAttr(attr)
if len(cameras) > 1:
if re.search(cls.R_CAMERA_TOKEN, file_prefix):