diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 8eb4d5799a..2be9174864 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1179,3 +1179,58 @@ def iter_parents(node): node = split[0] yield node + + +def remove_other_uv_sets(mesh): + """Remove all other UV sets than the current UV set. + + Keep only current UV set and ensure it's the renamed to default 'map1'. + + """ + + uvSets = cmds.polyUVSet(mesh, query=True, allUVSets=True) + current = cmds.polyUVSet(mesh, query=True, currentUVSet=True)[0] + + # Copy over to map1 + if current != 'map1': + cmds.polyUVSet(mesh, uvSet=current, newUVSet='map1', copy=True) + cmds.polyUVSet(mesh, currentUVSet=True, uvSet='map1') + current = 'map1' + + # Delete all non-current UV sets + deleteUVSets = [uvSet for uvSet in uvSets if uvSet != current] + uvSet = None + + # Maya Bug (tested in 2015/2016): + # In some cases the API's MFnMesh will report less UV sets than + # maya.cmds.polyUVSet. This seems to happen when the deletion of UV sets + # has not triggered a cleanup of the UVSet array attribute on the mesh + # node. It will still have extra entries in the attribute, though it will + # not show up in API or UI. Nevertheless it does show up in + # maya.cmds.polyUVSet. To ensure we clean up the array we'll force delete + # the extra remaining 'indices' that we don't want. + + # TODO: Implement a better fix + # The best way to fix would be to get the UVSet indices from api with + # MFnMesh (to ensure we keep correct ones) and then only force delete the + # other entries in the array attribute on the node. But for now we're + # deleting all entries except first one. Note that the first entry could + # never be removed (the default 'map1' always exists and is supposed to + # be undeletable.) + try: + for uvSet in deleteUVSets: + cmds.polyUVSet(mesh, delete=True, uvSet=uvSet) + except RuntimeError as exc: + log.warning('Error uvSet: %s - %s', uvSet, exc) + indices = cmds.getAttr('{0}.uvSet'.format(mesh), + multiIndices=True) + if not indices: + log.warning("No uv set found indices for: %s", mesh) + return + + # Delete from end to avoid shifting indices + # and remove the indices in the attribute + indices = reversed(indices[1:]) + for i in indices: + attr = '{0}.uvSet[{1}]'.format(mesh, i) + cmds.removeMultiInstance(attr, b=True) diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index abc9be046f..d62c34e915 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -10,7 +10,7 @@ from colorbleed.maya.lib import extract_alembic class ExtractColorbleedAnimation(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. - Positions and normals are preserved, but nothing more, + Positions and normals, uvs, creases are preserved, but nothing more, for plain and predictable point caches. """ @@ -49,18 +49,26 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) + options = { + "step": instance.data.get("step", 1.0), + "attr": ["cbId"], + "writeVisibility": True, + "writeCreases": True, + "uvWrite": True, + "selection": True + } + + if int(cmds.about(version=True)) >= 2017: + # Since Maya 2017 alembic supports multiple uv sets - write them. + options["writeUVSets"] = True + with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=start, endFrame=end, - **{"step": instance.data.get("step", 1.0), - "attr": ["cbId"], - "writeVisibility": True, - "writeCreases": True, - "uvWrite": True, - "selection": True}) + **options) if "files" not in instance.data: instance.data["files"] = list() diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 878bb14d6c..405d1e6549 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -10,7 +10,7 @@ from colorbleed.maya.lib import extract_alembic class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. - Positions and normals are preserved, but nothing more, + Positions and normals, uvs, creases are preserved, but nothing more, for plain and predictable point caches. """ @@ -44,19 +44,27 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) + options = { + "step": instance.data.get("step", 1.0), + "attr": ["cbId"], + "writeVisibility": True, + "writeCreases": True, + "writeColorSets": writeColorSets, + "uvWrite": True, + "selection": True + } + + if int(cmds.about(version=True)) >= 2017: + # Since Maya 2017 alembic supports multiple uv sets - write them. + options["writeUVSets"] = True + with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=start, endFrame=end, - **{"step": instance.data.get("step", 1.0), - "attr": ["cbId"], - "writeVisibility": True, - "writeCreases": True, - "writeColorSets": writeColorSets, - "uvWrite": True, - "selection": True}) + **options) if "files" not in instance.data: instance.data["files"] = list() diff --git a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py index 85567631d5..77ec1a0661 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py @@ -2,10 +2,17 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): - """Ensure no multiple UV sets exist for each polygon mesh""" + """Warn on multiple UV sets existing for each polygon mesh. + + On versions prior to Maya 2017 this will force no multiple uv sets because + the Alembic exports in Maya prior to 2017 don't support writing multiple + UV sets. + + """ order = colorbleed.api.ValidateMeshOrder hosts = ['maya'] @@ -42,83 +49,20 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Nodes found with multiple " - "UV sets: {0}".format(invalid)) + + message = "Nodes found with multiple UV sets: {0}".format(invalid) + + # Maya 2017 and up allows multiple UV sets in Alembic exports + # so we allow it, yet just warn the user to ensure they know about + # the other UV sets. + allowed = int(cmds.about(version=True)) >= 2017 + + if allowed: + self.log.warning(message) + else: + raise ValueError(message) @classmethod def repair(cls, instance): for mesh in cls.get_invalid(instance): - cls._repair_mesh(mesh) - - @classmethod - def _repair_mesh(cls, mesh): - """Process a single mesh, deleting other UV sets than the active one. - - Keep only current UV set and ensure it's the default 'map1' - - """ - from maya import cmds - - uvSets = cmds.polyUVSet(mesh, - query=True, - allUVSets=True) - current = cmds.polyUVSet(mesh, - query=True, - currentUVSet=True)[0] - - # Copy over to map1 - if current != 'map1': - cmds.polyUVSet(mesh, - uvSet=current, - newUVSet='map1', - copy=True) - cmds.polyUVSet(mesh, - currentUVSet=True, - uvSet='map1') - current = 'map1' - - # Delete all non-current UV sets - deleteUVSets = [uvSet for uvSet in uvSets if uvSet != current] - uvSet = None - - # Maya Bug (tested in 2015/2016): - # In some cases the API's MFnMesh will report less UV sets - # than maya.cmds.polyUVSet. - # This seems to happen when the deletion of UV sets has not - # triggered a cleanup of the UVSet array - # attribute on the mesh node. It will still have extra - # entries in the attribute, though it will not - # show up in API or UI. Nevertheless it does show up in - # maya.cmds.polyUVSet. - # To ensure we clean up the array we'll force delete the - # extra remaining 'indices' that we don't want. - - # TODO: Implement a better fix - # The best way to fix would be to get the UVSet - # indices from api with MFnMesh (to ensure we keep - # correct ones) and then only force delete the other - # entries in the array attribute on the node. - # But for now we're deleting all entries except first - # one. Note that the first entry could never - # be removed (the default 'map1' always exists and is - # supposed to be undeletable.) - try: - for uvSet in deleteUVSets: - cmds.polyUVSet(mesh, delete=True, uvSet=uvSet) - except RuntimeError, e: - cls.log.warning('uvSet: {0} - ' - 'Error: {1}'.format(uvSet, e)) - - indices = cmds.getAttr('{0}.uvSet'.format(mesh), - multiIndices=True) - if not indices: - cls.log.warning( - "No uv set found indices for: {0}".format(mesh)) - return - - # Delete from end to avoid shifting indices - # and remove the indices in the attribute - indices = reversed(indices[1:]) - for i in indices: - attr = '{0}.uvSet[{1}]'.format(mesh, i) - cmds.removeMultiInstance(attr, b=True) + lib.remove_other_uv_sets(mesh)