diff --git a/pype/plugins/maya/publish/collect_render.py b/pype/plugins/maya/publish/collect_render.py index 1188669d00..7d0e798ae9 100644 --- a/pype/plugins/maya/publish/collect_render.py +++ b/pype/plugins/maya/publish/collect_render.py @@ -59,6 +59,7 @@ R_LAYER_TOKEN = re.compile( r'.*%l.*|.*.*|.*.*', re.IGNORECASE) R_AOV_TOKEN = re.compile(r'.*%a.*|.*.*|.*.*', re.IGNORECASE) R_SUBSTITUTE_AOV_TOKEN = re.compile(r'%a||', re.IGNORECASE) +R_REMOVE_AOV_TOKEN = re.compile(r'_%a|_|_', re.IGNORECASE) R_SUBSTITUTE_LAYER_TOKEN = re.compile( r'%l||', re.IGNORECASE) R_SUBSTITUTE_CAMERA_TOKEN = re.compile(r'%c|', re.IGNORECASE) @@ -160,6 +161,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): # return all expected files for all cameras and aovs in given # frame range exp_files = ExpectedFiles().get(renderer, layer_name) + assert exp_files, ("no file names were generated, this is bug") # if we want to attach render to subset, check if we have AOV's # in expectedFiles. If so, raise error as we cannot attach AOV @@ -173,16 +175,32 @@ class CollectMayaRender(pyblish.api.ContextPlugin): full_exp_files = [] aov_dict = {} - for aov, files in exp_files[0].items(): + # we either get AOVs or just list of files. List of files can + # mean two things - there are no AOVs enabled or multipass EXR + # is produced. In either case we treat those as `beauty`. + if isinstance(exp_files[0], dict): + for aov, files in exp_files[0].items(): + full_paths = [] + for ef in files: + full_path = os.path.join(workspace, "renders", ef) + full_path = full_path.replace("\\", "/") + full_paths.append(full_path) + aov_dict[aov] = full_paths + else: full_paths = [] - for ef in files: + for ef in exp_files: full_path = os.path.join(workspace, "renders", ef) full_path = full_path.replace("\\", "/") full_paths.append(full_path) - aov_dict[aov] = full_paths + aov_dict["beauty"] = full_paths full_exp_files.append(aov_dict) + from pprint import pprint + print("=" * 40) + pprint(full_exp_files) + print("=" * 40) + self.log.info("collecting layer: {}".format(layer_name)) # Get layer specific settings, might be overrides data = { @@ -363,7 +381,15 @@ class AExpectedFiles: def get_aovs(self): pass - def get_files(self): + def get_renderer_prefix(self): + try: + file_prefix = cmds.getAttr(ImagePrefixes[self.renderer]) + except KeyError: + raise UnsupportedRendererException( + "Unsupported renderer {}".format(self.renderer)) + return file_prefix + + def _get_layer_data(self): # ______________________________________________ # ____________________/ ____________________________________________/ # 1 - get scene name /__________________/ @@ -381,11 +407,7 @@ class AExpectedFiles: # __________________/ ______________________________________________/ # 3 - image prefix /__________________/ # __________________/ - try: - file_prefix = cmds.getAttr(ImagePrefixes[renderer]) - except KeyError: - raise UnsupportedRendererException( - "Unsupported renderer {}".format(renderer)) + file_prefix = self.get_renderer_prefix() if not file_prefix: raise RuntimeError("Image prefix not set") @@ -397,6 +419,9 @@ class AExpectedFiles: # 4 - get renderable cameras_____________/ # __________________/ + # if we have token in prefix path we'll expect output for + # every renderable camera in layer. + renderable_cameras = self.get_renderable_cameras() # ________________________________________________ # __________________/ ______________________________________________/ @@ -404,11 +429,11 @@ class AExpectedFiles: # __________________/ enabled_aovs = self.get_aovs() + from pprint import pprint + print("-" * 40) + pprint(enabled_aovs) + print("-" * 40) - # if we have token in prefix path we'll expect output for - # every renderable camera in layer. - - expected_files = [] layer_name = self.layer if self.layer.startswith("rs_"): layer_name = self.layer[3:] @@ -417,62 +442,104 @@ class AExpectedFiles: frame_step = int(self.get_render_attribute('byFrameStep')) padding = int(self.get_render_attribute('extensionPadding')) - resolved_path = file_prefix - if enabled_aovs: - aov_file_list = {} - for aov in enabled_aovs: - for cam in renderable_cameras: + scene_data = { + "frameStart": start_frame, + "frameEnd": end_frame, + "frameStep": frame_step, + "padding": padding, + "cameras": renderable_cameras, + "sceneName": scene_name, + "layerName": layer_name, + "renderer": renderer, + "defaultExt": default_ext, + "filePrefix": file_prefix, + "enabledAOVs": enabled_aovs + } + return scene_data - mappings = ( - (R_SUBSTITUTE_SCENE_TOKEN, scene_name), - (R_SUBSTITUTE_LAYER_TOKEN, layer_name), - (R_SUBSTITUTE_CAMERA_TOKEN, cam), - (R_SUBSTITUTE_AOV_TOKEN, aov[0]) - ) + def _generate_single_file_sequence(self, layer_data): + expected_files = [] + file_prefix = layer_data["filePrefix"] + for cam in layer_data["cameras"]: + mappings = ( + (R_SUBSTITUTE_SCENE_TOKEN, layer_data["sceneName"]), + (R_SUBSTITUTE_LAYER_TOKEN, layer_data["layerName"]), + (R_SUBSTITUTE_CAMERA_TOKEN, cam), + # this is required to remove unfilled aov token, for example + # in Redshift + (R_REMOVE_AOV_TOKEN, "") + ) - for regex, value in mappings: - file_prefix = re.sub(regex, value, file_prefix) + for regex, value in mappings: + file_prefix = re.sub(regex, value, file_prefix) - aov_files = [] - for frame in range( - int(start_frame), - int(end_frame) + 1, - int(frame_step)): - aov_files.append( - '{}.{}.{}'.format( - file_prefix, - str(frame).rjust(padding, "0"), - aov[1])) + for frame in range( + int(layer_data["frameStart"]), + int(layer_data["frameEnd"]) + 1, + int(layer_data["frameStep"])): + expected_files.append( + '{}.{}.{}'.format(file_prefix, + str(frame).rjust( + layer_data["padding"], "0"), + layer_data["defaultExt"])) + return expected_files - # if we have more then one renderable camera, append - # camera name to AOV to allow per camera AOVs. - aov_name = aov[0] - if len(renderable_cameras) > 1: - aov_name = "{}_{}".format(aov[0], cam) + def _generate_aov_file_sequences(self, layer_data): + expected_files = [] + aov_file_list = {} + file_prefix = layer_data["filePrefix"] + for aov in layer_data["enabledAOVs"]: + for cam in layer_data["cameras"]: - aov_file_list[aov_name] = aov_files - file_prefix = resolved_path - - expected_files.append(aov_file_list) - else: - for cam in renderable_cameras: mappings = ( - (R_SUBSTITUTE_SCENE_TOKEN, scene_name), - (R_SUBSTITUTE_LAYER_TOKEN, layer_name), - (R_SUBSTITUTE_CAMERA_TOKEN, cam) + (R_SUBSTITUTE_SCENE_TOKEN, layer_data["sceneName"]), + (R_SUBSTITUTE_LAYER_TOKEN, layer_data["layerName"]), + (R_SUBSTITUTE_CAMERA_TOKEN, cam), + (R_SUBSTITUTE_AOV_TOKEN, aov[0]) ) for regex, value in mappings: file_prefix = re.sub(regex, value, file_prefix) + aov_files = [] for frame in range( - int(start_frame), - int(end_frame) + 1, - int(frame_step)): - expected_files.append( - '{}.{}.{}'.format(file_prefix, - str(frame).rjust(padding, "0"), - default_ext)) + int(layer_data["frameStart"]), + int(layer_data["frameEnd"]) + 1, + int(layer_data["frameStep"])): + aov_files.append( + '{}.{}.{}'.format( + file_prefix, + str(frame).rjust(layer_data["padding"], "0"), + aov[1])) + + # if we have more then one renderable camera, append + # camera name to AOV to allow per camera AOVs. + aov_name = aov[0] + if len(layer_data["cameras"]) > 1: + aov_name = "{}_{}".format(aov[0], cam) + + aov_file_list[aov_name] = aov_files + file_prefix = layer_data["filePrefix"] + + expected_files.append(aov_file_list) + return expected_files + + def get_files(self): + """ + This method will return list of expected files. + + It will translate render token strings ('', etc.) to + their values. This task is tricky as every renderer deals with this + differently. It depends on `get_aovs()` abstract method implemented + for every supported renderer. + """ + layer_data = self._get_layer_data() + + expected_files = [] + if layer_data.get("enabledAOVs"): + expected_files = self._generate_aov_file_sequences(layer_data) + else: + expected_files = self._generate_single_file_sequence(layer_data) return expected_files @@ -656,13 +723,53 @@ class ExpectedFilesVray(AExpectedFiles): class ExpectedFilesRedshift(AExpectedFiles): + # mapping redshift extension dropdown values to strings + ext_mapping = ['iff', 'exr', 'tif', 'png', 'tga', 'jpg'] + def __init__(self, layer): super(ExpectedFilesRedshift, self).__init__(layer) self.renderer = 'redshift' + def get_renderer_prefix(self): + prefix = super(ExpectedFilesRedshift, self).get_renderer_prefix() + prefix = "{}_".format(prefix) + return prefix + + def get_files(self): + expected_files = super(ExpectedFilesRedshift, self).get_files() + + # we need to add one sequence for plain beauty if AOVs are enabled. + # as redshift output beauty without 'beauty' in filename. + + layer_data = self._get_layer_data() + if layer_data.get("enabledAOVs"): + expected_files[0][u"beauty"] = self._generate_single_file_sequence(layer_data) # noqa: E501 + + return expected_files + def get_aovs(self): enabled_aovs = [] - default_ext = cmds.getAttr('defaultRenderGlobals.imfPluginKey') + + try: + if self.maya_is_true( + cmds.getAttr("redshiftOptions.exrForceMultilayer")): + # AOVs are merged in mutli-channel file + print("*" * 40) + print(cmds.getAttr("redshiftOptions.exrForceMultilayer")) + print("*" * 40) + return enabled_aovs + except ValueError: + # this occurs when Render Setting windows was not opened yet. In + # such case there are no Arnold options created so query for AOVs + # will fail. We terminate here as there are no AOVs specified then. + # This state will most probably fail later on some Validator + # anyway. + print("+" * 40) + return enabled_aovs + + default_ext = self.ext_mapping[ + cmds.getAttr('redshiftOptions.imageFormat') + ] rs_aovs = [n for n in cmds.ls(type='RedshiftAOV')] # todo: find out how to detect multichannel exr for redshift @@ -674,7 +781,6 @@ class ExpectedFilesRedshift(AExpectedFiles): enabled = self.maya_is_true(override) if enabled: - # todo: find how redshift set format for AOVs enabled_aovs.append( ( cmds.getAttr('%s.name' % aov), diff --git a/pype/plugins/maya/publish/validate_rendersettings.py b/pype/plugins/maya/publish/validate_rendersettings.py index b74199352a..923ee185c6 100644 --- a/pype/plugins/maya/publish/validate_rendersettings.py +++ b/pype/plugins/maya/publish/validate_rendersettings.py @@ -48,6 +48,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): 'redshift': 'defaultRenderGlobals.imageFilePrefix' } + ImagePrefixTokens = { + + 'arnold': 'maya///_//', + 'vray': 'maya///' + } + R_AOV_TOKEN = re.compile( r'%a||', re.IGNORECASE) R_LAYER_TOKEN = re.compile( @@ -99,26 +106,31 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): "doesn't have: '' or " "'' token".format(prefix)) - if not re.search(cls.R_AOV_TOKEN, prefix): - invalid = True - cls.log.error("Wrong image prefix [ {} ] - " - "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)) + # renderer specific checks if renderer == "vray": - if prefix.lower() != cls.VRAY_PREFIX.lower(): - cls.log.warning("warning: prefix differs from " - "recommended {}".format(cls.VRAY_PREFIX)) + # no vray checks implemented yet + pass + elif renderer == "redshift": + # no redshift check implemented yet + pass else: - if prefix.lower() != cls.DEFAULT_PREFIX.lower(): - cls.log.warning("warning: prefix differs from " - "recommended {}".format(cls.DEFAULT_PREFIX)) + if not re.search(cls.R_AOV_TOKEN, prefix): + invalid = True + cls.log.error("Wrong image prefix [ {} ] - " + "doesn't have: '' or " + "token".format(prefix)) + + # prefix check + if prefix.lower() != cls.ImagePrefixTokens[renderer].lower(): + cls.log.warning("warning: prefix differs from " + "recommended {}".format( + cls.ImagePrefixTokens[renderer])) if padding != cls.DEFAULT_PADDING: invalid = True @@ -127,21 +139,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): return invalid - @classmethod - def get_prefix(cls, renderer): - prefix = cls.RENDERER_PREFIX.get(renderer, cls.DEFAULT_PREFIX) - # maya.cmds and pymel.core return only default project directory and - # not the current one but only default. - output_path = os.path.join( - mel.eval("workspace -q -rd;"), pm.workspace.fileRules["images"] - ) - # Workfile paths can be configured to have host name in file path. - # In this case we want to avoid duplicate folder names. - if "maya" in output_path.lower(): - prefix = prefix.replace("maya/", "") - - return prefix - @classmethod def repair(cls, instance): @@ -156,7 +153,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): node = render_attrs["node"] prefix_attr = render_attrs["prefix"] - fname_prefix = cls.get_prefix(renderer) + fname_prefix = cls.ImagePrefixTokens[renderer] cmds.setAttr("{}.{}".format(node, prefix_attr), fname_prefix, type="string")