From 893a84526c8308c12cdbc7f915470735a1d8faa0 Mon Sep 17 00:00:00 2001 From: Bo Zhou Date: Fri, 18 Mar 2022 19:15:07 +0900 Subject: [PATCH 01/18] add new render product for 3delight --- openpype/hosts/maya/api/lib_renderproducts.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 0c34998874..69c4eae18e 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1107,6 +1107,81 @@ class RenderProductsRenderman(ARenderProducts): return new_files +class RenderProducts3Delight(ARenderProducts): + """Expected files for Renderman renderer. + + Warning: + This is very rudimentary and needs more love and testing. + """ + + renderer = "_3delight" + + def get_render_products(self): + """Get all AOVs. + + See Also: + :func:`ARenderProducts.get_render_products()` + + """ + cameras = [ + self.sanitize_camera_name(c) + for c in self.get_renderable_cameras() + ] + + if not cameras: + cameras = [ + self.sanitize_camera_name( + self.get_renderable_cameras()[0]) + ] + products = [] + + default_ext = "exr" + + nodes = cmds.listConnections('dlRenderGlobals1') + assert len(nodes) == 1 + node = nodes[0] + + num_layers = cmds.getAttr( + '{}.layerOutput'.format(node), + size=True) + for i in range(num_layers): + output = cmds.getAttr( + '{}.layerOutput[{}]'.format(node, i)) + if not output: + continue + + output_var = cmds.getAttr( + '{}.layerOutputVariables[{}]'.format(node, i)) + output_var_tokens = layerOutputVariable.split('|') + name = output_var_tokens[4] + + for camera in cameras: + product = RenderProduct(productName=name, + ext=default_ext, + camera=camera) + products.append(product) + + return products + + def get_files(self, product, camera): + """Get expected files. + + See Also: + :func:`ARenderProducts.get_files()` + """ + files = super(RenderProducts3Delight, self).get_files(product, camera) + + layer_data = self.layer_data + new_files = [] + for file in files: + new_file = "{}/{}/{}".format( + layer_data["sceneName"], layer_data["layerName"], file + ) + new_files.append(new_file) + + return new_files + + class AOVError(Exception): """Custom exception for determining AOVs.""" From e85ef95b443825badf6b30e598712ba24cd6aeb0 Mon Sep 17 00:00:00 2001 From: Bo Zhou Date: Fri, 18 Mar 2022 20:00:57 +0900 Subject: [PATCH 02/18] improve 3delight render product class and return it --- openpype/hosts/maya/api/lib_renderproducts.py | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 69c4eae18e..97aa8e8957 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -77,6 +77,7 @@ IMAGE_PREFIXES = { "arnold": "defaultRenderGlobals.imageFilePrefix", "renderman": "rmanGlobals.imageFileFormat", "redshift": "defaultRenderGlobals.imageFilePrefix", + "_3delight": "defaultRenderGlobals.imageFilePrefix", } @@ -154,7 +155,8 @@ def get(layer, render_instance=None): "arnold": RenderProductsArnold, "vray": RenderProductsVray, "redshift": RenderProductsRedshift, - "renderman": RenderProductsRenderman + "renderman": RenderProductsRenderman, + "_3delight": RenderProducts3Delight }.get(renderer_name.lower(), None) if renderer is None: raise UnsupportedRendererException( @@ -1137,13 +1139,16 @@ class RenderProducts3Delight(ARenderProducts): default_ext = "exr" - nodes = cmds.listConnections('dlRenderGlobals1') + nodes = cmds.listConnections( + 'dlRenderGlobals1', + type='dlRenderSettings') assert len(nodes) == 1 node = nodes[0] num_layers = cmds.getAttr( - '{}.layerOutput'.format(node), + '{}.layerOutputVariables'.format(node), size=True) + assert num_layers > 0 for i in range(num_layers): output = cmds.getAttr( '{}.layerOutput[{}]'.format(node, i)) @@ -1152,35 +1157,17 @@ class RenderProducts3Delight(ARenderProducts): output_var = cmds.getAttr( '{}.layerOutputVariables[{}]'.format(node, i)) - output_var_tokens = layerOutputVariable.split('|') - name = output_var_tokens[4] + output_var_tokens = output_var.split('|') + aov_name = output_var_tokens[4] for camera in cameras: - product = RenderProduct(productName=name, + product = RenderProduct(productName=aov_name, ext=default_ext, camera=camera) products.append(product) return products - def get_files(self, product, camera): - """Get expected files. - - See Also: - :func:`ARenderProducts.get_files()` - """ - files = super(RenderProducts3Delight, self).get_files(product, camera) - - layer_data = self.layer_data - new_files = [] - for file in files: - new_file = "{}/{}/{}".format( - layer_data["sceneName"], layer_data["layerName"], file - ) - new_files.append(new_file) - - return new_files - class AOVError(Exception): """Custom exception for determining AOVs.""" From 3ec621a8efa97bc33c63f33fd87a62a76fa7d0c2 Mon Sep 17 00:00:00 2001 From: Bo Zhou Date: Fri, 18 Mar 2022 20:02:29 +0900 Subject: [PATCH 03/18] add method _set_3delight_settings to render creator --- .../maya/plugins/create/create_render.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_render.py b/openpype/hosts/maya/plugins/create/create_render.py index 9002ae3876..c06fe8a76d 100644 --- a/openpype/hosts/maya/plugins/create/create_render.py +++ b/openpype/hosts/maya/plugins/create/create_render.py @@ -77,7 +77,8 @@ class CreateRender(plugin.Creator): 'vray': 'vraySettings.fileNamePrefix', 'arnold': 'defaultRenderGlobals.imageFilePrefix', 'renderman': 'defaultRenderGlobals.imageFilePrefix', - 'redshift': 'defaultRenderGlobals.imageFilePrefix' + 'redshift': 'defaultRenderGlobals.imageFilePrefix', + '_3delight': 'defaultRenderGlobals.imageFilePrefix' } _image_prefixes = { @@ -85,7 +86,8 @@ class CreateRender(plugin.Creator): 'vray': 'maya///', 'arnold': 'maya///{aov_separator}', # noqa 'renderman': 'maya///{aov_separator}', - 'redshift': 'maya///' # noqa + 'redshift': 'maya///', # noqa + '_3delight': 'maya///' # noqa } _aov_chars = { @@ -462,6 +464,8 @@ class CreateRender(plugin.Creator): asset["data"].get("resolutionHeight")) self._set_global_output_settings() + if renderer == "_3delight": + self._set_3delight_settings(asset) def _set_vray_settings(self, asset): # type: (dict) -> None @@ -507,6 +511,38 @@ class CreateRender(plugin.Creator): "{}.height".format(node), asset["data"].get("resolutionHeight")) + def _set_3delight_settings(self, asset): + # type: (dict) -> None + """Sets important settings for 3Delight.""" + nodes = cmds.listConnections( + 'dlRenderGlobals1', + type='dlRenderSettings') + assert len(nodes) == 1 + node = nodes[0] + + # frame range + start_frame = int(cmds.playbackOptions(query=True, + animationStartTime=True)) + end_frame = int(cmds.playbackOptions(query=True, + animationEndTime=True)) + + cmds.setAttr( + "{}.startFrame".format(node), start_frame) + cmds.setAttr( + "{}.endFrame".format(node), end_frame) + + # outputOptionsDefault + cmds.setAttr( + "{}.outputOptionsDefault".format(node), 2) + + # resolution + cmds.setAttr( + "defaultResolution.width", + asset["data"].get("resolutionWidth")) + cmds.setAttr( + "defaultResolution.height", + asset["data"].get("resolutionHeight")) + @staticmethod def _set_global_output_settings(): # enable animation From 87c697b51bd42655ef3936187240b4a2681efaac Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 22 Jun 2022 13:52:02 +0900 Subject: [PATCH 04/18] Rewrote large portions of the file to handle different nodes that may contain texture files. Currently supported are: - file (maya) - aiImage (Arnold) - RedshiftNormalMap (Redshift) - dlTexture (3Delight) - dlTriplanar (3Delight) --- .../publish/collect_multiverse_look.py | 250 +++++++++++------- 1 file changed, 148 insertions(+), 102 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index edf40a27a6..4bd2476feb 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -21,37 +21,68 @@ COLOUR_SPACES = ['sRGB', 'linear', 'auto'] MIPMAP_EXTENSIONS = ['tdl'] -def get_look_attrs(node): - """Returns attributes of a node that are important for the look. +class _NodeTypeAttrib(object): + """docstring for _NodeType""" - These are the "changed" attributes (those that have edits applied - in the current scene). + def __init__(self, name, fname, computed_fname=None, colour_space=None): + self.name = name + self.fname = fname + self.computed_fname = computed_fname or fname + self.colour_space = colour_space or "colorSpace" - Returns: - list: Attribute names to extract + def get_fname(self, node): + return "{}.{}".format(node, self.fname) + def get_computed_fname(self, node): + return "{}.{}".format(node, self.computed_fname) + + def get_colour_space(self, node): + return "{}.{}".format(node, self.colour_space) + + def __str__(self): + return "_NodeTypeAttrib(name={}, fname={}, " + "computed_fname={}, colour_space={})".format( + self.name, self.fname, self.computed_fname, self.colour_space) + + +NODETYPES = { + "file": [_NodeTypeAttrib("file", "fileTextureName", + "computedFileTextureNamePattern")], + "aiImage": [_NodeTypeAttrib("aiImage", "filename")], + "RedshiftNormalMap": [_NodeTypeAttrib("RedshiftNormalMap", "tex0")], + "dlTexture": [_NodeTypeAttrib("dlTexture", "textureFile", + None, "textureFile_meta_colorspace")], + "dlTriplanar": [_NodeTypeAttrib("dlTriplanar", "colorTexture", + None, "colorTexture_meta_colorspace"), + _NodeTypeAttrib("dlTriplanar", "floatTexture", + None, "floatTexture_meta_colorspace"), + _NodeTypeAttrib("dlTriplanar", "heightTexture", + None, "heightTexture_meta_colorspace")] +} + + +def get_file_paths_for_node(node): + """Gets all the file paths in this node. + + Returns all filepaths that this node references. Some node types only + reference one, but others, like dlTriplanar, can reference 3. + + Args: + node (str): Name of the Maya node + + Returns + list(str): A list with all evaluated maya attributes for filepaths. """ - # When referenced get only attributes that are "changed since file open" - # which includes any reference edits, otherwise take *all* user defined - # attributes - is_referenced = cmds.referenceQuery(node, isNodeReferenced=True) - result = cmds.listAttr(node, userDefined=True, - changedSinceFileOpen=is_referenced) or [] - # `cbId` is added when a scene is saved, ignore by default - if "cbId" in result: - result.remove("cbId") + node_type = cmds.nodeType(node) + if node_type not in NODETYPES: + return [] - # For shapes allow render stat changes - if cmds.objectType(node, isAType="shape"): - attrs = cmds.listAttr(node, changedSinceFileOpen=True) or [] - for attr in attrs: - if attr in SHAPE_ATTRS: - result.append(attr) - elif attr.startswith('ai'): - result.append(attr) - - return result + paths = [] + for node_type_attr in NODETYPES[node_type]: + fname = cmds.getAttr("{}.{}".format(node, node_type_attr.fname)) + paths.append(fname) + return paths def node_uses_image_sequence(node): @@ -69,13 +100,29 @@ def node_uses_image_sequence(node): """ # useFrameExtension indicates an explicit image sequence - node_path = get_file_node_path(node).lower() + paths = get_file_node_paths(node) + paths = [path.lower() for path in paths] # The following tokens imply a sequence patterns = ["", "", "", "u_v", ""] lower = texture_pattern.lower() if any(pattern in lower for pattern in patterns): - return texture_pattern + return [texture_pattern] - if cmds.nodeType(node) == 'aiImage': - return cmds.getAttr('{0}.filename'.format(node)) - if cmds.nodeType(node) == 'RedshiftNormalMap': - return cmds.getAttr('{}.tex0'.format(node)) - - # otherwise use fileTextureName - return cmds.getAttr('{0}.fileTextureName'.format(node)) + return get_file_paths_for_node(node) def get_file_node_files(node): @@ -181,15 +222,13 @@ def get_file_node_files(node): """ - path = get_file_node_path(node) - path = cmds.workspace(expandName=path) + paths = get_file_node_paths(node) + paths = [cmds.workspace(expandName=path) for path in paths] if node_uses_image_sequence(node): - glob_pattern = seq_to_glob(path) - return glob.glob(glob_pattern) - elif os.path.exists(path): - return [path] + globs = [glob.glob(seq_to_glob(path)) for path in paths] + return globs else: - return [] + return list(filter(lambda x: os.path.exists(x), paths)) def get_mipmap(fname): @@ -211,6 +250,11 @@ def is_mipmap(fname): class CollectMultiverseLookData(pyblish.api.InstancePlugin): """Collect Multiverse Look + Searches through the overrides finding all material overrides. From there + it extracts the shading group and then finds all texture files in the + shading group network. It also checks for mipmap versions of texture files + and adds them to the resouces to get published. + """ order = pyblish.api.CollectorOrder + 0.2 @@ -258,12 +302,20 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): shadingGroup), "members": list()} # The SG may reference files, add those too! - history = cmds.listHistory(shadingGroup) - files = cmds.ls(history, type="file", long=True) + history = cmds.listHistory( + shadingGroup, allConnections=True) + + # We need to iterate over node_types since `cmds.ls` may + # error out if we don't have the appropriate plugin loaded. + files = [] + for node_type in NODETYPES.keys(): + files += cmds.ls(history, + type=node_type, + long=True) for f in files: resources = self.collect_resource(f, publishMipMap) - instance.data["resources"].append(resources) + instance.data["resources"] += resources elif isinstance(matOver, multiverse.MaterialSourceUsdPath): # TODO: Handle this later. @@ -284,69 +336,63 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): dict """ - self.log.debug("processing: {}".format(node)) - if cmds.nodeType(node) not in ["file", "aiImage", "RedshiftNormalMap"]: - self.log.error( - "Unsupported file node: {}".format(cmds.nodeType(node))) + node_type = cmds.nodeType(node) + self.log.debug("processing: {}/{}".format(node, node_type)) + + if not node_type in NODETYPES: + self.log.error("Unsupported file node: {}".format(node_type)) raise AssertionError("Unsupported file node") - if cmds.nodeType(node) == 'file': - self.log.debug(" - file node") - attribute = "{}.fileTextureName".format(node) - computed_attribute = "{}.computedFileTextureNamePattern".format( - node) - elif cmds.nodeType(node) == 'aiImage': - self.log.debug("aiImage node") - attribute = "{}.filename".format(node) - computed_attribute = attribute - elif cmds.nodeType(node) == 'RedshiftNormalMap': - self.log.debug("RedshiftNormalMap node") - attribute = "{}.tex0".format(node) - computed_attribute = attribute + resources = [] + for node_type_attr in NODETYPES[node_type]: + fname_attrib = node_type_attr.get_fname(node) + computed_fname_attrib = node_type_attr.get_computed_fname(node) + colour_space_attrib = node_type_attr.get_colour_space(node) - source = cmds.getAttr(attribute) - self.log.info(" - file source: {}".format(source)) - color_space_attr = "{}.colorSpace".format(node) - try: - color_space = cmds.getAttr(color_space_attr) - except ValueError: - # node doesn't have colorspace attribute + source = cmds.getAttr(fname_attrib) color_space = "Raw" - # Compare with the computed file path, e.g. the one with the - # pattern in it, to generate some logging information about this - # difference - # computed_attribute = "{}.computedFileTextureNamePattern".format(node) - computed_source = cmds.getAttr(computed_attribute) - if source != computed_source: - self.log.debug("Detected computed file pattern difference " - "from original pattern: {0} " - "({1} -> {2})".format(node, - source, - computed_source)) + try: + color_space = cmds.getAttr(colour_space_attrib) + except ValueError: + # node doesn't have colorspace attribute, use "Raw" from before + pass + # Compare with the computed file path, e.g. the one with the + # pattern in it, to generate some logging information about this + # difference + # computed_attribute = "{}.computedFileTextureNamePattern".format(node) + computed_source = cmds.getAttr(computed_fname_attrib) + if source != computed_source: + self.log.debug("Detected computed file pattern difference " + "from original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) - # We replace backslashes with forward slashes because V-Ray - # can't handle the UDIM files with the backslashes in the - # paths as the computed patterns - source = source.replace("\\", "/") + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = source.replace("\\", "/") - files = get_file_node_files(node) - files = self.handle_files(files, publishMipMap) - if len(files) == 0: - self.log.error("No valid files found from node `%s`" % node) + files = get_file_node_files(node) + files = self.handle_files(files, publishMipMap) + if len(files) == 0: + self.log.error("No valid files found from node `%s`" % node) - self.log.info("collection of resource done:") - self.log.info(" - node: {}".format(node)) - self.log.info(" - attribute: {}".format(attribute)) - self.log.info(" - source: {}".format(source)) - self.log.info(" - file: {}".format(files)) - self.log.info(" - color space: {}".format(color_space)) + self.log.info("collection of resource done:") + self.log.info(" - node: {}".format(node)) + self.log.info(" - attribute: {}".format(fname_attrib)) + self.log.info(" - source: {}".format(source)) + self.log.info(" - file: {}".format(files)) + self.log.info(" - color space: {}".format(color_space)) - # Define the resource - return {"node": node, - "attribute": attribute, - "source": source, # required for resources - "files": files, - "color_space": color_space} # required for resources + # Define the resource + resource = {"node": node, + "attribute": fname_attrib, + "source": source, # required for resources + "files": files, + "color_space": color_space} # required for resources + resources.append(resource) + return resources def handle_files(self, files, publishMipMap): """This will go through all the files and make sure that they are From 2007c759478b08b002a0d0a91816c5cf0e769c3f Mon Sep 17 00:00:00 2001 From: DMO Date: Fri, 24 Jun 2022 15:22:31 +0900 Subject: [PATCH 05/18] Reverting `mvUsd` family back to `usd` so that other software can create standard Usd files and still be imported directly by Multiverse. --- openpype/hosts/maya/plugins/create/create_multiverse_usd.py | 2 +- openpype/hosts/maya/plugins/load/load_multiverse_usd.py | 2 +- openpype/hosts/maya/plugins/load/load_reference.py | 3 ++- openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py | 2 +- openpype/plugins/publish/integrate_new.py | 1 - 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py index 5290d5143f..8cd76b5f40 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd.py @@ -6,7 +6,7 @@ class CreateMultiverseUsd(plugin.Creator): name = "mvUsdMain" label = "Multiverse USD Asset" - family = "mvUsd" + family = "usd" icon = "cubes" def __init__(self, *args, **kwargs): diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py index 3350dc6ac9..76d7c306a0 100644 --- a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py @@ -16,7 +16,7 @@ from openpype.hosts.maya.api.pipeline import containerise class MultiverseUsdLoader(load.LoaderPlugin): """Read USD data in a Multiverse Compound""" - families = ["model", "mvUsd", "mvUsdComposition", "mvUsdOverride", + families = ["model", "usd", "mvUsdComposition", "mvUsdOverride", "pointcache", "animation"] representations = ["usd", "usda", "usdc", "usdz", "abc"] diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index e4355ed3d4..0a2640014c 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -25,7 +25,8 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): "rig", "camerarig", "xgen", - "staticMesh"] + "staticMesh", + "mvLook"] representations = ["ma", "abc", "fbx", "mb"] label = "Reference" diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 3654be7b34..b1aaf9d9ba 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -26,7 +26,7 @@ class ExtractMultiverseUsd(openpype.api.Extractor): label = "Extract Multiverse USD Asset" hosts = ["maya"] - families = ["mvUsd"] + families = ["usd"] scene_type = "usd" file_formats = ["usd", "usda", "usdz"] diff --git a/openpype/plugins/publish/integrate_new.py b/openpype/plugins/publish/integrate_new.py index 2471105250..f5ca125189 100644 --- a/openpype/plugins/publish/integrate_new.py +++ b/openpype/plugins/publish/integrate_new.py @@ -110,7 +110,6 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "staticMesh", "skeletalMesh", "mvLook", - "mvUsd", "mvUsdComposition", "mvUsdOverride", "simpleUnrealTexture" From ab1c86a96dda5eb92281e810027d8952c91b0a31 Mon Sep 17 00:00:00 2001 From: DMO Date: Tue, 28 Jun 2022 15:40:45 +0900 Subject: [PATCH 06/18] Do not lock nodes, it's not needed and it makes things much harder. --- openpype/hosts/maya/plugins/load/load_multiverse_usd.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py index 76d7c306a0..24b97db365 100644 --- a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py @@ -47,9 +47,6 @@ class MultiverseUsdLoader(load.LoaderPlugin): transform = cmds.listRelatives( shape, parent=True, fullPath=True)[0] - # Lock the shape node so the user cannot delete it. - cmds.lockNode(shape, lock=True) - nodes = [transform, shape] self[:] = nodes From c5fd2a970eab972b87468d5c3f7e796b3c11c2e1 Mon Sep 17 00:00:00 2001 From: DMO Date: Tue, 28 Jun 2022 17:13:18 +0900 Subject: [PATCH 07/18] I was incorrectly globing into an array of arrays instead of single long array. --- .../hosts/maya/plugins/publish/collect_multiverse_look.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index 4bd2476feb..b11dbaeba6 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -225,7 +225,9 @@ def get_file_node_files(node): paths = get_file_node_paths(node) paths = [cmds.workspace(expandName=path) for path in paths] if node_uses_image_sequence(node): - globs = [glob.glob(seq_to_glob(path)) for path in paths] + globs = [] + for path in paths: + globs += glob.glob(seq_to_glob(path)) return globs else: return list(filter(lambda x: os.path.exists(x), paths)) From 044946e8c5f65a8a4ae308862b2ed5d57494e501 Mon Sep 17 00:00:00 2001 From: DMO Date: Tue, 28 Jun 2022 20:47:28 +0900 Subject: [PATCH 08/18] Strip UsdComp namespaces. Write attributes from mvLook. Formatting. --- .../hosts/maya/plugins/create/create_multiverse_usd_comp.py | 2 +- openpype/hosts/maya/plugins/publish/collect_look.py | 3 ++- .../hosts/maya/plugins/publish/collect_multiverse_look.py | 2 +- .../hosts/maya/plugins/publish/extract_multiverse_look.py | 2 +- .../hosts/maya/plugins/publish/validate_mvlook_contents.py | 5 +++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py index ed466a8068..a92969eb9a 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py @@ -17,7 +17,7 @@ class CreateMultiverseUsdComp(plugin.Creator): # Order of `fileFormat` must match extract_multiverse_usd_comp.py self.data["fileFormat"] = ["usda", "usd"] - self.data["stripNamespaces"] = False + self.data["stripNamespaces"] = True self.data["mergeTransformAndShape"] = False self.data["flattenContent"] = False self.data["writeAsCompoundLayers"] = False diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index e8ada57f8f..28c57e04b5 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -440,7 +440,8 @@ class CollectLook(pyblish.api.InstancePlugin): for res in self.collect_resources(n): instance.data["resources"].append(res) - self.log.info("Collected resources: {}".format(instance.data["resources"])) + self.log.info("Collected resources: {}".format( + instance.data["resources"])) # Log warning when no relevant sets were retrieved for the look. if ( diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index b11dbaeba6..4c50e4df27 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -256,7 +256,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): it extracts the shading group and then finds all texture files in the shading group network. It also checks for mipmap versions of texture files and adds them to the resouces to get published. - + """ order = pyblish.api.CollectorOrder + 0.2 diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py index 82e2b41929..b97314d5a1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -73,7 +73,7 @@ class ExtractMultiverseLook(openpype.api.Extractor): "writeAll": False, "writeTransforms": False, "writeVisibility": False, - "writeAttributes": False, + "writeAttributes": True, "writeMaterials": True, "writeVariants": False, "writeVariantsDefinition": False, diff --git a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py index bac2c030c8..a755ec1da9 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_mvlook_contents.py @@ -80,13 +80,14 @@ class ValidateMvLookContents(pyblish.api.InstancePlugin): def is_or_has_mipmap(self, fname, files): ext = os.path.splitext(fname)[1][1:] if ext in MIPMAP_EXTENSIONS: - self.log.debug("Is a mipmap '{}'".format(fname)) + self.log.debug(" - Is a mipmap '{}'".format(fname)) return True for colour_space in COLOUR_SPACES: for mipmap_ext in MIPMAP_EXTENSIONS: mipmap_fname = '.'.join([fname, colour_space, mipmap_ext]) if mipmap_fname in files: - self.log.debug("Has a mipmap '{}'".format(fname)) + self.log.debug( + " - Has a mipmap '{}'".format(mipmap_fname)) return True return False From cb82e66d01c506bd9038acec6904a0a7b39cbb37 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 6 Jul 2022 11:30:47 +0900 Subject: [PATCH 09/18] Naming suffixes can be overridden in project settings, lets print out what they *actually* are. --- .../publish/validate_transform_naming_suffix.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py index 6f5ff24b9c..51561b35e1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -20,6 +20,7 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): - nurbsSurface: _NRB - locator: _LOC - null/group: _GRP + Suffices can also be overriden by project settings. .. warning:: This grabs the first child shape as a reference and doesn't use the @@ -43,6 +44,13 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): ALLOW_IF_NOT_IN_SUFFIX_TABLE = True + @classmethod + def get_table_for_invalid(cls): + ss = [] + for k,v in cls.SUFFIX_NAMING_TABLE.items(): + ss.append(" - {}: {}".format(k,", ".join(v))) + return "\n".join(ss) + @staticmethod def is_valid_name(node_name, shape_type, SUFFIX_NAMING_TABLE, ALLOW_IF_NOT_IN_SUFFIX_TABLE): @@ -105,5 +113,7 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): """ invalid = self.get_invalid(instance) if invalid: + valid = self.get_table_for_invalid() raise ValueError("Incorrectly named geometry " - "transforms: {0}".format(invalid)) + "transforms: {0}, accepted suffixes are: " + "\n{1}".format(invalid, valid)) From 56f13bf388ad42a4a34e533fe5ea529ffd7f6668 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 6 Jul 2022 12:39:40 +0900 Subject: [PATCH 10/18] Move alembic publishing options around. Adding more publishing options to the schema and defaults. --- .../defaults/project_settings/maya.json | 22 +++--- .../schemas/schema_maya_publish.json | 72 ++++++++++++------- 2 files changed, 62 insertions(+), 32 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index cdd3a62d00..82d5ab20cb 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -401,14 +401,6 @@ "optional": true, "active": true }, - "ExtractAlembic": { - "enabled": true, - "families": [ - "pointcache", - "model", - "vrayproxy" - ] - }, "ValidateRigContents": { "enabled": false, "optional": true, @@ -561,6 +553,20 @@ "optional": true, "active": true, "bake_attributes": [] + }, + "ExtractAlembic": { + "enabled": true, + "families": [ + "pointcache", + "model", + "vrayproxy" + ] + }, + "ExtractAnimation": { + "enabled": true + }, + "ExtractMultiverseUsdAnim": { + "enabled": true } }, "load": { 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 41b681d893..e77bb1a6f8 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 @@ -504,30 +504,6 @@ "label": "ValidateUniqueNames" } ] - }, - { - "type": "label", - "label": "Extractors" - }, - { - "type": "dict", - "collapsible": true, - "key": "ExtractAlembic", - "label": "Extract Alembic", - "checkbox_key": "enabled", - "children": [ - { - "type": "boolean", - "key": "enabled", - "label": "Enabled" - }, - { - "key": "families", - "label": "Families", - "type": "list", - "object_type": "text" - } - ] } ] }, @@ -686,6 +662,54 @@ "is_list": true } ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractAlembic", + "label": "Extract Alembic", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractAnimation", + "label": "Extract Alembic Animation", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractMultiverseUsdAnim", + "label": "Extract USD Animation", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] } ] } From 53f56853d9519b96115ab92e10aa610d776cd5be Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 6 Jul 2022 12:43:24 +0900 Subject: [PATCH 11/18] Subclass ExtractMultiverseUsd as ExtractMultiverseUsdAnim to enable animation-specific USD extraction. Adding noNormals to settings to allow skipping normals for meshes that will be sub-divided later. --- .../maya/plugins/create/create_animation.py | 3 + .../maya/plugins/publish/extract_animation.py | 4 +- .../plugins/publish/extract_multiverse_usd.py | 70 ++++++++++++++++--- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index 5cd1f7090a..b9e1f3b7a2 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -42,3 +42,6 @@ class CreateAnimation(plugin.Creator): # Default to not send to farm. self.data["farm"] = False self.data["priority"] = 50 + + # Default to write normals. + self.data["writeNormals"] = True diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index abe5ed3bf5..7ce80b4679 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -63,7 +63,9 @@ class ExtractAnimation(openpype.api.Extractor): "selection": True, "worldSpace": instance.data.get("worldSpace", True), "writeColorSets": instance.data.get("writeColorSets", False), - "writeFaceSets": instance.data.get("writeFaceSets", False) + "writeFaceSets": instance.data.get("writeFaceSets", False), + # 'noNormals' is the standard alembic option name. + "noNormals": not instance.data.get("writeNormals", True) } if not instance.data.get("includeParentHierarchy", True): diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index b1aaf9d9ba..2a99dffa8d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -2,6 +2,7 @@ import os import six from maya import cmds +from maya import mel import openpype.api from openpype.hosts.maya.api.lib import maintained_selection @@ -87,7 +88,7 @@ class ExtractMultiverseUsd(openpype.api.Extractor): return { "stripNamespaces": False, "mergeTransformAndShape": False, - "writeAncestors": True, + "writeAncestors": False, "flattenParentXforms": False, "writeSparseOverrides": False, "useMetaPrimPath": False, @@ -147,6 +148,13 @@ class ExtractMultiverseUsd(openpype.api.Extractor): return options + def get_default_options(self): + self.log.info("ExtractMultiverseUsd get_default_options") + return self.default_options + + def filter_members(self, members): + return members + def process(self, instance): # Load plugin first cmds.loadPlugin("MultiverseForMaya", quiet=True) @@ -161,7 +169,7 @@ class ExtractMultiverseUsd(openpype.api.Extractor): file_path = file_path.replace('\\', '/') # Parse export options - options = self.default_options + options = self.get_default_options() options = self.parse_overrides(instance, options) self.log.info("Export options: {0}".format(options)) @@ -170,27 +178,35 @@ class ExtractMultiverseUsd(openpype.api.Extractor): with maintained_selection(): members = instance.data("setMembers") - self.log.info('Collected object {}'.format(members)) + self.log.info('Collected objects: {}'.format(members)) + members = self.filter_members(members) + if not members: + self.log.error('No members!') + return + self.log.info(' - filtered: {}'.format(members)) import multiverse time_opts = None frame_start = instance.data['frameStart'] frame_end = instance.data['frameEnd'] - handle_start = instance.data['handleStart'] - handle_end = instance.data['handleEnd'] - step = instance.data['step'] - fps = instance.data['fps'] if frame_end != frame_start: time_opts = multiverse.TimeOptions() time_opts.writeTimeRange = True + + handle_start = instance.data['handleStart'] + handle_end = instance.data['handleEnd'] + time_opts.frameRange = ( frame_start - handle_start, frame_end + handle_end) - time_opts.frameIncrement = step - time_opts.numTimeSamples = instance.data["numTimeSamples"] - time_opts.timeSamplesSpan = instance.data["timeSamplesSpan"] - time_opts.framePerSecond = fps + time_opts.frameIncrement = instance.data['step'] + time_opts.numTimeSamples = instance.data.get( + 'numTimeSamples', options['numTimeSamples']) + time_opts.timeSamplesSpan = instance.data.get( + 'timeSamplesSpan', options['timeSamplesSpan']) + time_opts.framePerSecond = instance.data.get( + 'fps', mel.eval('currentTimeUnitToFPS()')) asset_write_opts = multiverse.AssetWriteOptions(time_opts) options_discard_keys = { @@ -203,11 +219,15 @@ class ExtractMultiverseUsd(openpype.api.Extractor): 'step', 'fps' } + self.log.debug("Write Options:") for key, value in options.items(): if key in options_discard_keys: continue + + self.log.debug(" - {}={}".format(key, value)) setattr(asset_write_opts, key, value) + self.log.info('WriteAsset: {} / {}'.format(file_path, members)) multiverse.WriteAsset(file_path, members, asset_write_opts) if "representations" not in instance.data: @@ -223,3 +243,31 @@ class ExtractMultiverseUsd(openpype.api.Extractor): self.log.info("Extracted instance {} to {}".format( instance.name, file_path)) + + +class ExtractMultiverseUsdAnim(ExtractMultiverseUsd): + """Extractor for Multiverse USD Animation Sparse Cache data. + + This will extract the sparse cache data from the scene and generate a + USD file with all the animation data. + + Upon publish a .usd sparse cache will be written. + """ + label = "Extract Multiverse USD Animation Sparse Cache" + families = ["animation"] + + def get_default_options(self): + anim_options = self.default_options + anim_options["writeSparseOverrides"] = True + anim_options["stripNamespaces"] = True + return anim_options + + def filter_members(self, members): + out_set = next((i for i in members if i.endswith("out_SET")), None) + + if out_set is None: + self.log.warning("Expecting out_SET") + return None + + members = cmds.ls(cmds.sets(out_set, query=True), long=True) + return members From cdf165ed79c7b9f3939d3b199d0757f9bac74e5b Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 6 Jul 2022 12:43:46 +0900 Subject: [PATCH 12/18] The mvLook should skip namespaces. --- openpype/hosts/maya/plugins/publish/extract_multiverse_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py index b97314d5a1..8a5d7e4e53 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -78,7 +78,7 @@ class ExtractMultiverseLook(openpype.api.Extractor): "writeVariants": False, "writeVariantsDefinition": False, "writeActiveState": False, - "writeNamespaces": False, + "writeNamespaces": True, "numTimeSamples": 1, "timeSamplesSpan": 0.0 } From 559210ffb6ffacd68be8227d56b90c036a70c184 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 6 Jul 2022 12:44:24 +0900 Subject: [PATCH 13/18] If there's a mixed-attribute on the node, this will fail, just skip it with a warning. --- openpype/hosts/maya/plugins/publish/collect_look.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/collect_look.py b/openpype/hosts/maya/plugins/publish/collect_look.py index 28c57e04b5..40edd3b2f4 100644 --- a/openpype/hosts/maya/plugins/publish/collect_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_look.py @@ -549,6 +549,11 @@ class CollectLook(pyblish.api.InstancePlugin): if not cmds.attributeQuery(attr, node=node, exists=True): continue attribute = "{}.{}".format(node, attr) + # We don't support mixed-type attributes yet. + if cmds.attributeQuery(attr, node=node, multi=True): + self.log.warning("Attribute '{}' is mixed-type and is " + "not supported yet.".format(attribute)) + continue if cmds.getAttr(attribute, type=True) == "message": continue node_attributes[attr] = cmds.getAttr(attribute) From 77c45e2b7f110e6939b685b03c613f7feef7f718 Mon Sep 17 00:00:00 2001 From: DMO Date: Wed, 20 Jul 2022 11:04:17 +0900 Subject: [PATCH 14/18] Animation should also write out UsdAttributes. --- openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 2a99dffa8d..40dd5dfe50 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -259,6 +259,7 @@ class ExtractMultiverseUsdAnim(ExtractMultiverseUsd): def get_default_options(self): anim_options = self.default_options anim_options["writeSparseOverrides"] = True + anim_options["writeUsdAttributes"] = True anim_options["stripNamespaces"] = True return anim_options From aeee2a491821133549672a98a0939e402fb7ce12 Mon Sep 17 00:00:00 2001 From: DMO Date: Mon, 31 Oct 2022 14:51:44 +0900 Subject: [PATCH 15/18] Fixed commit for adding proper layered support for USD overrides. --- .../create/create_multiverse_usd_comp.py | 2 +- .../maya/plugins/load/load_multiverse_usd.py | 32 ++++- .../plugins/load/load_multiverse_usd_over.py | 136 ++++++++++++++++++ 3 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py diff --git a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py index a92969eb9a..ed466a8068 100644 --- a/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/create/create_multiverse_usd_comp.py @@ -17,7 +17,7 @@ class CreateMultiverseUsdComp(plugin.Creator): # Order of `fileFormat` must match extract_multiverse_usd_comp.py self.data["fileFormat"] = ["usda", "usd"] - self.data["stripNamespaces"] = True + self.data["stripNamespaces"] = False self.data["mergeTransformAndShape"] = False self.data["flattenContent"] = False self.data["writeAsCompoundLayers"] = False diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py index 24b97db365..13915aa2a9 100644 --- a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- import maya.cmds as cmds +from maya import mel +import os from openpype.pipeline import ( load, @@ -11,6 +13,7 @@ from openpype.hosts.maya.api.lib import ( unique_namespace ) from openpype.hosts.maya.api.pipeline import containerise +from openpype.client import get_representations, get_representation_by_id class MultiverseUsdLoader(load.LoaderPlugin): @@ -26,7 +29,6 @@ class MultiverseUsdLoader(load.LoaderPlugin): color = "orange" def load(self, context, name=None, namespace=None, options=None): - asset = context['asset']['name'] namespace = namespace or unique_namespace( asset + "_", @@ -34,15 +36,16 @@ class MultiverseUsdLoader(load.LoaderPlugin): suffix="_", ) - # Create the shape + # Make sure we can load the plugin cmds.loadPlugin("MultiverseForMaya", quiet=True) + import multiverse + # Create the shape shape = None transform = None with maintained_selection(): cmds.namespace(addNamespace=namespace) with namespaced(namespace, new=False): - import multiverse shape = multiverse.CreateUsdCompound(self.fname) transform = cmds.listRelatives( shape, parent=True, fullPath=True)[0] @@ -67,15 +70,34 @@ class MultiverseUsdLoader(load.LoaderPlugin): shapes = cmds.ls(members, type="mvUsdCompoundShape") assert shapes, "Cannot find mvUsdCompoundShape in container" - path = get_representation_path(representation) + project_name = representation["context"]["project"]["name"] + prev_representation_id = cmds.getAttr("{}.representation".format(node)) + prev_representation = get_representation_by_id(project_name, + prev_representation_id) + prev_path = os.path.normpath(prev_representation["data"]["path"]) + # Make sure we can load the plugin + cmds.loadPlugin("MultiverseForMaya", quiet=True) import multiverse + for shape in shapes: - multiverse.SetUsdCompoundAssetPaths(shape, [path]) + + asset_paths = multiverse.GetUsdCompoundAssetPaths(shape) + asset_paths = [os.path.normpath(p) for p in asset_paths] + + assert asset_paths.count(prev_path) == 1, \ + "Couldn't find matching path (or too many)" + prev_path_idx = asset_paths.index(prev_path) + + path = get_representation_path(representation) + asset_paths[prev_path_idx] = path + + multiverse.SetUsdCompoundAssetPaths(shape, asset_paths) cmds.setAttr("{}.representation".format(node), str(representation["_id"]), type="string") + mel.eval('refreshEditorTemplates;') def switch(self, container, representation): self.update(container, representation) diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py new file mode 100644 index 0000000000..080475461b --- /dev/null +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +import maya.cmds as cmds +from maya import mel +import os + +import qargparse + +from openpype.pipeline import ( + load, + get_representation_path +) +from openpype.hosts.maya.api.lib import ( + maintained_selection, + namespaced, + unique_namespace +) +from openpype.hosts.maya.api.pipeline import containerise +from openpype.client import get_representations, get_representation_by_id + + +class MultiverseUsdOverLoader(load.LoaderPlugin): + """Reference file""" + + families = ["mvUsdOverride"] + representations = ["usda", "usd", "udsz"] + + label = "Load Usd Override into Compound" + order = -10 + icon = "code-fork" + color = "orange" + + options = [ + qargparse.String( + "Which Compound", + label="Compound", + help="Select which compound to add this as a layer to." + ) + ] + + def load(self, context, name=None, namespace=None, options=None): + asset = context['asset']['name'] + + current_usd = cmds.ls(selection=True, + type="mvUsdCompoundShape", + dag=True, + long=True) + if len(current_usd) != 1: + self.log.error("Current selection invalid: '{}', " + "must contain exactly 1 mvUsdCompoundShape." + "".format(current_usd)) + return + + # Make sure we can load the plugin + cmds.loadPlugin("MultiverseForMaya", quiet=True) + import multiverse + + nodes = current_usd + with maintained_selection(): + multiverse.AddUsdCompoundAssetPath(current_usd[0], self.fname) + + namespace = current_usd[0].split("|")[1].split(":")[0] + + container = containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + cmds.addAttr(container, longName="mvUsdCompoundShape", + niceName="mvUsdCompoundShape", dataType="string") + cmds.setAttr(container + ".mvUsdCompoundShape", + current_usd[0], type="string") + + return container + + def update(self, container, representation): + # type: (dict, dict) -> None + """Update container with specified representation.""" + + cmds.loadPlugin("MultiverseForMaya", quiet=True) + import multiverse + + node = container['objectName'] + assert cmds.objExists(node), "Missing container" + + members = cmds.sets(node, query=True) or [] + shapes = cmds.ls(members, type="mvUsdCompoundShape") + assert shapes, "Cannot find mvUsdCompoundShape in container" + + mvShape = container['mvUsdCompoundShape'] + assert mvShape, "Missing mv source" + + project_name = representation["context"]["project"]["name"] + prev_representation_id = cmds.getAttr("{}.representation".format(node)) + prev_representation = get_representation_by_id(project_name, + prev_representation_id) + prev_path = os.path.normpath(prev_representation["data"]["path"]) + + path = get_representation_path(representation) + + for shape in shapes: + asset_paths = multiverse.GetUsdCompoundAssetPaths(shape) + asset_paths = [os.path.normpath(p) for p in asset_paths] + + assert asset_paths.count(prev_path) == 1, \ + "Couldn't find matching path (or too many)" + prev_path_idx = asset_paths.index(prev_path) + asset_paths[prev_path_idx] = path + multiverse.SetUsdCompoundAssetPaths(shape, asset_paths) + + cmds.setAttr("{}.representation".format(node), + str(representation["_id"]), + type="string") + mel.eval('refreshEditorTemplates;') + + def switch(self, container, representation): + self.update(container, representation) + + def remove(self, container): + # type: (dict) -> None + """Remove loaded container.""" + # Delete container and its contents + if cmds.objExists(container['objectName']): + members = cmds.sets(container['objectName'], query=True) or [] + cmds.delete([container['objectName']] + members) + + # Remove the namespace, if empty + namespace = container['namespace'] + if cmds.namespace(exists=namespace): + members = cmds.namespaceInfo(namespace, listNamespace=True) + if not members: + cmds.namespace(removeNamespace=namespace) + else: + self.log.warning("Namespace not deleted because it " + "still has members: %s", namespace) From c62527a678d97f9bf0e6be6f4ecec3d30433104b Mon Sep 17 00:00:00 2001 From: DMO Date: Mon, 31 Oct 2022 15:15:46 +0900 Subject: [PATCH 16/18] Removing bad merge. --- openpype/hosts/maya/api/lib_renderproducts.py | 58 ------------------- 1 file changed, 58 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 7b9601cda8..cd204445b7 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -77,7 +77,6 @@ IMAGE_PREFIXES = { "arnold": "defaultRenderGlobals.imageFilePrefix", "renderman": "rmanGlobals.imageFileFormat", "redshift": "defaultRenderGlobals.imageFilePrefix", - "_3delight": "defaultRenderGlobals.imageFilePrefix", "mayahardware2": "defaultRenderGlobals.imageFilePrefix" } @@ -173,7 +172,6 @@ def get(layer, render_instance=None): "redshift": RenderProductsRedshift, "renderman": RenderProductsRenderman, "mayahardware2": RenderProductsMayaHardware - "_3delight": RenderProducts3Delight }.get(renderer_name.lower(), None) if renderer is None: raise UnsupportedRendererException( @@ -1288,62 +1286,6 @@ class RenderProductsMayaHardware(ARenderProducts): for cam in self.get_renderable_cameras(): product = RenderProduct(productName="beauty", ext=ext, camera=cam) products.append(product) -class RenderProducts3Delight(ARenderProducts): - """Expected files for Renderman renderer. - - Warning: - This is very rudimentary and needs more love and testing. - """ - - renderer = "_3delight" - - def get_render_products(self): - """Get all AOVs. - - See Also: - :func:`ARenderProducts.get_render_products()` - - """ - cameras = [ - self.sanitize_camera_name(c) - for c in self.get_renderable_cameras() - ] - - if not cameras: - cameras = [ - self.sanitize_camera_name( - self.get_renderable_cameras()[0]) - ] - products = [] - - default_ext = "exr" - - nodes = cmds.listConnections( - 'dlRenderGlobals1', - type='dlRenderSettings') - assert len(nodes) == 1 - node = nodes[0] - - num_layers = cmds.getAttr( - '{}.layerOutputVariables'.format(node), - size=True) - assert num_layers > 0 - for i in range(num_layers): - output = cmds.getAttr( - '{}.layerOutput[{}]'.format(node, i)) - if not output: - continue - - output_var = cmds.getAttr( - '{}.layerOutputVariables[{}]'.format(node, i)) - output_var_tokens = output_var.split('|') - aov_name = output_var_tokens[4] - - for camera in cameras: - product = RenderProduct(productName=aov_name, - ext=default_ext, - camera=camera) - products.append(product) return products From 422a3c4f8ad9e7cbdb590d4c9f5aeb694cbcc698 Mon Sep 17 00:00:00 2001 From: DMO Date: Mon, 31 Oct 2022 15:33:50 +0900 Subject: [PATCH 17/18] Removed wrongly-staged `extract_animation.py` --- .../maya/plugins/publish/extract_animation.py | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/extract_animation.py diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py deleted file mode 100644 index 3d1f30b640..0000000000 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ /dev/null @@ -1,113 +0,0 @@ -import os - -from maya import cmds - -import openpype.api -from openpype.hosts.maya.api.lib import ( - extract_alembic, - suspended_refresh, - maintained_selection, - iter_visible_nodes_in_range -) - - -class ExtractAnimation(openpype.api.Extractor): - """Produce an alembic of just point positions and normals. - - Positions and normals, uvs, creases are preserved, but nothing more, - for plain and predictable point caches. - - Plugin can run locally or remotely (on a farm - if instance is marked with - "farm" it will be skipped in local processing, but processed on farm) - """ - - label = "Extract Animation" - hosts = ["maya"] - families = ["animation"] - targets = ["local", "remote"] - - def process(self, instance): - if instance.data.get("farm"): - self.log.debug("Should be processed on farm, skipping.") - return - - # Collect the out set nodes - out_sets = [node for node in instance if node.endswith("out_SET")] - if len(out_sets) != 1: - raise RuntimeError("Couldn't find exactly one out_SET: " - "{0}".format(out_sets)) - out_set = out_sets[0] - roots = cmds.sets(out_set, query=True) - - # Include all descendants - nodes = roots + cmds.listRelatives(roots, - allDescendents=True, - fullPath=True) or [] - - # Collect the start and end including handles - start = instance.data["frameStartHandle"] - end = instance.data["frameEndHandle"] - - self.log.info("Extracting animation..") - dirname = self.staging_dir(instance) - - parent_dir = self.staging_dir(instance) - filename = "{name}.abc".format(**instance.data) - path = os.path.join(parent_dir, filename) - - options = { - "step": instance.data.get("step", 1.0) or 1.0, - "attr": ["cbId"], - "writeVisibility": True, - "writeCreases": True, - "uvWrite": True, - "selection": True, - "worldSpace": instance.data.get("worldSpace", True), - "writeColorSets": instance.data.get("writeColorSets", False), - "writeFaceSets": instance.data.get("writeFaceSets", False), - # 'noNormals' is the standard alembic option name. - "noNormals": not instance.data.get("writeNormals", True) - } - - if not instance.data.get("includeParentHierarchy", True): - # Set the root nodes if we don't want to include parents - # The roots are to be considered the ones that are the actual - # direct members of the set - options["root"] = roots - - if int(cmds.about(version=True)) >= 2017: - # Since Maya 2017 alembic supports multiple uv sets - write them. - options["writeUVSets"] = True - - if instance.data.get("visibleOnly", False): - # If we only want to include nodes that are visible in the frame - # range then we need to do our own check. Alembic's `visibleOnly` - # flag does not filter out those that are only hidden on some - # frames as it counts "animated" or "connected" visibilities as - # if it's always visible. - nodes = list(iter_visible_nodes_in_range(nodes, - start=start, - end=end)) - - with suspended_refresh(): - with maintained_selection(): - cmds.select(nodes, noExpand=True) - extract_alembic(file=path, - startFrame=float(start), - endFrame=float(end), - **options) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': 'abc', - 'ext': 'abc', - 'files': filename, - "stagingDir": dirname, - } - instance.data["representations"].append(representation) - - instance.context.data["cleanupFullPaths"].append(path) - - self.log.info("Extracted {} to {}".format(instance, dirname)) From 046ed724c802101ad7a4190ae7fb9f3765b907d3 Mon Sep 17 00:00:00 2001 From: DMO Date: Mon, 31 Oct 2022 15:40:25 +0900 Subject: [PATCH 18/18] Addressing valid hound concerns. --- openpype/hosts/maya/plugins/load/load_multiverse_usd.py | 2 +- .../hosts/maya/plugins/load/load_multiverse_usd_over.py | 8 ++------ .../hosts/maya/plugins/publish/collect_multiverse_look.py | 6 +++--- .../plugins/publish/validate_transform_naming_suffix.py | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py index 13915aa2a9..9e0d38df46 100644 --- a/openpype/hosts/maya/plugins/load/load_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd.py @@ -13,7 +13,7 @@ from openpype.hosts.maya.api.lib import ( unique_namespace ) from openpype.hosts.maya.api.pipeline import containerise -from openpype.client import get_representations, get_representation_by_id +from openpype.client import get_representation_by_id class MultiverseUsdLoader(load.LoaderPlugin): diff --git a/openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py b/openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py index 080475461b..8a25508ac2 100644 --- a/openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/load/load_multiverse_usd_over.py @@ -10,12 +10,10 @@ from openpype.pipeline import ( get_representation_path ) from openpype.hosts.maya.api.lib import ( - maintained_selection, - namespaced, - unique_namespace + maintained_selection ) from openpype.hosts.maya.api.pipeline import containerise -from openpype.client import get_representations, get_representation_by_id +from openpype.client import get_representation_by_id class MultiverseUsdOverLoader(load.LoaderPlugin): @@ -38,8 +36,6 @@ class MultiverseUsdOverLoader(load.LoaderPlugin): ] def load(self, context, name=None, namespace=None, options=None): - asset = context['asset']['name'] - current_usd = cmds.ls(selection=True, type="mvUsdCompoundShape", dag=True, diff --git a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py index 4c50e4df27..a7cb14855b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/collect_multiverse_look.py @@ -253,7 +253,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): """Collect Multiverse Look Searches through the overrides finding all material overrides. From there - it extracts the shading group and then finds all texture files in the + it extracts the shading group and then finds all texture files in the shading group network. It also checks for mipmap versions of texture files and adds them to the resouces to get published. @@ -341,7 +341,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): node_type = cmds.nodeType(node) self.log.debug("processing: {}/{}".format(node, node_type)) - if not node_type in NODETYPES: + if node_type not in NODETYPES: self.log.error("Unsupported file node: {}".format(node_type)) raise AssertionError("Unsupported file node") @@ -361,7 +361,7 @@ class CollectMultiverseLookData(pyblish.api.InstancePlugin): # Compare with the computed file path, e.g. the one with the # pattern in it, to generate some logging information about this # difference - # computed_attribute = "{}.computedFileTextureNamePattern".format(node) + # computed_attribute = "{}.computedFileTextureNamePattern".format(node) # noqa computed_source = cmds.getAttr(computed_fname_attrib) if source != computed_source: self.log.debug("Detected computed file pattern difference " diff --git a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py index 4f2a400d91..65551c8d5e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py +++ b/openpype/hosts/maya/plugins/publish/validate_transform_naming_suffix.py @@ -48,8 +48,8 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): @classmethod def get_table_for_invalid(cls): ss = [] - for k,v in cls.SUFFIX_NAMING_TABLE.items(): - ss.append(" - {}: {}".format(k,", ".join(v))) + for k, v in cls.SUFFIX_NAMING_TABLE.items(): + ss.append(" - {}: {}".format(k, ", ".join(v))) return "\n".join(ss) @staticmethod