From da7c47ab2fe6c9a87de6491de2cf71abd7fc98b2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Sep 2023 14:47:22 +0800 Subject: [PATCH 01/59] add fbx extractors and new sets in rig family --- openpype/hosts/maya/api/fbx.py | 4 +- .../hosts/maya/plugins/create/create_rig.py | 22 ++++- .../plugins/publish/collect_rig_for_fbx.py | 45 +++++++++ .../maya/plugins/publish/extract_rig_fbx.py | 92 +++++++++++++++++++ 4 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_rig_fbx.py diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 260241f5fc..bd0e77e427 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -63,6 +63,7 @@ class FBXExtractor: "embeddedTextures": bool, "inputConnections": bool, "upAxis": str, # x, y or z, + "referencedAssetsContent": bool, "triangulate": bool } @@ -104,7 +105,8 @@ class FBXExtractor: "embeddedTextures": False, "inputConnections": True, "upAxis": "y", - "triangulate": False + "referencedAssetsContent": False, + "triangulate": False, } def __init__(self, log=None): diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 345ab6c00d..9b67c84980 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -1,6 +1,7 @@ from maya import cmds from openpype.hosts.maya.api import plugin +from openpype.lib import BoolDef class CreateRig(plugin.MayaCreator): @@ -12,6 +13,7 @@ class CreateRig(plugin.MayaCreator): icon = "wheelchair" def create(self, subset_name, instance_data, pre_create_data): + instance_data["fbx_enabled"] = pre_create_data.get("fbx_enabled") instance = super(CreateRig, self).create(subset_name, instance_data, @@ -20,6 +22,24 @@ class CreateRig(plugin.MayaCreator): instance_node = instance.get("instance_node") self.log.info("Creating Rig instance set up ...") + # change name controls = cmds.sets(name=subset_name + "_controls_SET", empty=True) + # change name pointcache = cmds.sets(name=subset_name + "_out_SET", empty=True) - cmds.sets([controls, pointcache], forceElement=instance_node) + if pre_create_data.get("fbx_enabled"): + skeleton = cmds.sets(name=subset_name + "_skeleton_SET", empty=True) + skeleton_mesh = cmds.sets(name=subset_name + "_skeletonMesh_SET", empty=True) + cmds.sets([controls, pointcache, + skeleton, skeleton_mesh], forceElement=instance_node) + else: + cmds.sets([controls, pointcache], forceElement=instance_node) + + def get_pre_create_attr_defs(self): + attrs = super(CreateRig, self).get_pre_create_attr_defs() + + return attrs + [ + BoolDef("fbx_enabled", + label="Fbx Export", + default=False), + + ] diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py new file mode 100644 index 0000000000..c57045a052 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +from maya import cmds # noqa +import pyblish.api + + +class CollectRigFbx(pyblish.api.InstancePlugin): + """Collect Unreal Skeletal Mesh.""" + + order = pyblish.api.CollectorOrder + 0.2 + label = "Collect rig for fbx" + families = ["rig"] + + def process(self, instance): + if not instance.data.get("fbx_enabled"): + self.log.debug("Skipping collecting rig data for fbx..") + return + + frame = cmds.currentTime(query=True) + instance.data["frameStart"] = frame + instance.data["frameEnd"] = frame + skeleton_sets = [ + i for i in instance[:] + if i.lower().endswith("skeleton_set") + ] + + skeleton_mesh_sets = [ + i for i in instance[:] + if i.lower().endswith("skeletonmesh_set") + ] + if skeleton_sets or skeleton_mesh_sets: + instance.data["families"] += ["fbx"] + instance.data["geometries"] = [] + instance.data["control_rigs"] = [] + instance.data["skeleton_mesh"] = [] + for skeleton_set in skeleton_sets: + skeleton_content = cmds.ls( + cmds.sets(skeleton_set, query=True), long=True) + if skeleton_content: + instance.data["control_rigs"] += skeleton_content + + for skeleton_mesh_set in skeleton_mesh_sets: + skeleton_mesh_content = cmds.ls( + cmds.sets(skeleton_mesh_set, query=True), long=True) + if skeleton_mesh_content: + instance.data["skeleton_mesh"] += skeleton_mesh_content diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py new file mode 100644 index 0000000000..da1a458c9e --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +import os + +from maya import cmds # noqa +import maya.mel as mel # noqa +import pyblish.api + +from openpype.pipeline import publish +from openpype.hosts.maya.api.lib import maintained_selection +from openpype.hosts.maya.api import fbx + + +class ExtractRigFBX(publish.Extractor): + """Extract Rig in FBX format from Maya. + + This extracts the rig in fbx with the constraints + and referenced asset content included. + This also optionally extract animated rig in fbx with + geometries included. + + """ + order = pyblish.api.ExtractorOrder + label = "Extract Rig (FBX)" + families = ["rig"] + + def process(self, instance): + if not instance.data.get("fbx_enabled"): + self.log.debug("fbx extractor has been disable.." + "Skipping the action...") + return + + # Define output path + staging_dir = self.staging_dir(instance) + filename = "{0}.fbx".format(instance.name) + path = os.path.join(staging_dir, filename) + + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace('\\', '/') + + self.log.debug("Extracting FBX to: {0}".format(path)) + + control_rigs = instance.data.get("control_rigs",[]) + skeletal_mesh = instance.data.get("skeleton_mesh", []) + members = control_rigs + skeletal_mesh + self._to_extract(instance, path, members) + + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'fbx', + 'ext': 'fbx', + 'files': filename, + "stagingDir": staging_dir, + } + instance.data["representations"].append(representation) + + self.log.debug("Extract FBX successful to: {0}".format(path)) + if skeletal_mesh: + self._to_extract(instance, path, skeletal_mesh) + representation = { + 'name': 'fbxanim', + 'ext': 'fbx', + 'files': filename, + "stagingDir": staging_dir, + "outputName": "fbxanim" + } + instance.data["representations"].append(representation) + self.log.debug("Extract animated FBX successful to: {0}".format(path)) + + def _to_extract(self, instance, path, members): + fbx_exporter = fbx.FBXExtractor(log=self.log) + control_rigs = instance.data.get("control_rigs",[]) + skeletal_mesh = instance.data.get("skeleton_mesh", []) + static_sets = control_rigs + skeletal_mesh + if members == static_sets: + instance.data["constraints"] = True + instance.data["referencedAssetsContent"] = True + if members == skeletal_mesh: + instance.data["constraints"] = True + instance.data["referencedAssetsContent"] = True + instance.data["animationOnly"] = True + + fbx_exporter.set_options_from_instance(instance) + + # Export + with maintained_selection(): + fbx_exporter.export(members, path) + cmds.select(members, r=1, noExpand=True) + mel.eval('FBXExport -f "{}" -s'.format(path)) From b54f263d4b7e977442b3087d0565da8a52770784 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Sep 2023 14:55:52 +0800 Subject: [PATCH 02/59] hound --- openpype/hosts/maya/plugins/create/create_rig.py | 6 ++++-- .../hosts/maya/plugins/publish/collect_rig_for_fbx.py | 1 - openpype/hosts/maya/plugins/publish/extract_rig_fbx.py | 8 ++++---- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 9b67c84980..030aa23a22 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -27,8 +27,10 @@ class CreateRig(plugin.MayaCreator): # change name pointcache = cmds.sets(name=subset_name + "_out_SET", empty=True) if pre_create_data.get("fbx_enabled"): - skeleton = cmds.sets(name=subset_name + "_skeleton_SET", empty=True) - skeleton_mesh = cmds.sets(name=subset_name + "_skeletonMesh_SET", empty=True) + skeleton = cmds.sets( + name=subset_name + "_skeleton_SET", empty=True) + skeleton_mesh = cmds.sets( + name=subset_name + "_skeletonMesh_SET", empty=True) cmds.sets([controls, pointcache, skeleton, skeleton_mesh], forceElement=instance_node) else: diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index c57045a052..bef43aa5f4 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -28,7 +28,6 @@ class CollectRigFbx(pyblish.api.InstancePlugin): if i.lower().endswith("skeletonmesh_set") ] if skeleton_sets or skeleton_mesh_sets: - instance.data["families"] += ["fbx"] instance.data["geometries"] = [] instance.data["control_rigs"] = [] instance.data["skeleton_mesh"] = [] diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index da1a458c9e..687b686fb8 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -40,12 +40,11 @@ class ExtractRigFBX(publish.Extractor): self.log.debug("Extracting FBX to: {0}".format(path)) - control_rigs = instance.data.get("control_rigs",[]) + control_rigs = instance.data.get("control_rigs", []) skeletal_mesh = instance.data.get("skeleton_mesh", []) members = control_rigs + skeletal_mesh self._to_extract(instance, path, members) - if "representations" not in instance.data: instance.data["representations"] = [] @@ -68,11 +67,12 @@ class ExtractRigFBX(publish.Extractor): "outputName": "fbxanim" } instance.data["representations"].append(representation) - self.log.debug("Extract animated FBX successful to: {0}".format(path)) + self.log.debug( + "Extract animated FBX successful to: {0}".format(path)) def _to_extract(self, instance, path, members): fbx_exporter = fbx.FBXExtractor(log=self.log) - control_rigs = instance.data.get("control_rigs",[]) + control_rigs = instance.data.get("control_rigs", []) skeletal_mesh = instance.data.get("skeleton_mesh", []) static_sets = control_rigs + skeletal_mesh if members == static_sets: From d58b5a42f7f792d1a8198cf500fa5fb31752e4f2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Sep 2023 20:46:39 +0800 Subject: [PATCH 03/59] implment fbx extractors in both animation and rig family --- openpype/hosts/maya/api/fbx.py | 12 ++-- .../hosts/maya/plugins/create/create_rig.py | 30 +++------ .../plugins/publish/collect_fbx_animation.py | 27 ++++++++ .../plugins/publish/collect_rig_for_fbx.py | 10 +-- .../plugins/publish/extract_fbx_animation.py | 60 +++++++++++++++++ .../maya/plugins/publish/extract_rig_fbx.py | 56 ++++------------ .../plugins/publish/validate_rig_contents.py | 66 ++++++++++++++++++- .../publish/validate_rig_controllers.py | 8 ++- ...idate_rig_controllers_arnold_attributes.py | 3 +- .../publish/validate_rig_out_set_node_ids.py | 19 +++++- .../publish/validate_rig_output_ids.py | 10 ++- 11 files changed, 213 insertions(+), 88 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/collect_fbx_animation.py create mode 100644 openpype/hosts/maya/plugins/publish/extract_fbx_animation.py diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index bd0e77e427..064ba00f08 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -6,6 +6,7 @@ from pyblish.api import Instance from maya import cmds # noqa import maya.mel as mel # noqa +from openpype.hosts.maya.api.lib import maintained_selection class FBXExtractor: @@ -63,8 +64,8 @@ class FBXExtractor: "embeddedTextures": bool, "inputConnections": bool, "upAxis": str, # x, y or z, - "referencedAssetsContent": bool, - "triangulate": bool + "triangulate": bool, + "exportFileVersion": str } @property @@ -105,8 +106,8 @@ class FBXExtractor: "embeddedTextures": False, "inputConnections": True, "upAxis": "y", - "referencedAssetsContent": False, "triangulate": False, + "exportFileVersion": "FBX201000" } def __init__(self, log=None): @@ -200,5 +201,6 @@ class FBXExtractor: path (str): Path to use for export. """ - cmds.select(members, r=True, noExpand=True) - mel.eval('FBXExport -f "{}" -s'.format(path)) + with maintained_selection(): + cmds.select(members, r=True, noExpand=True) + mel.eval('FBXExport -f "{}" -s'.format(path)) diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 030aa23a22..b4ff6fad07 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -13,7 +13,6 @@ class CreateRig(plugin.MayaCreator): icon = "wheelchair" def create(self, subset_name, instance_data, pre_create_data): - instance_data["fbx_enabled"] = pre_create_data.get("fbx_enabled") instance = super(CreateRig, self).create(subset_name, instance_data, @@ -22,26 +21,13 @@ class CreateRig(plugin.MayaCreator): instance_node = instance.get("instance_node") self.log.info("Creating Rig instance set up ...") - # change name + # change name (_controls_set -> _rigs_SET) controls = cmds.sets(name=subset_name + "_controls_SET", empty=True) - # change name + # change name (_out_SET -> _geo_SET) pointcache = cmds.sets(name=subset_name + "_out_SET", empty=True) - if pre_create_data.get("fbx_enabled"): - skeleton = cmds.sets( - name=subset_name + "_skeleton_SET", empty=True) - skeleton_mesh = cmds.sets( - name=subset_name + "_skeletonMesh_SET", empty=True) - cmds.sets([controls, pointcache, - skeleton, skeleton_mesh], forceElement=instance_node) - else: - cmds.sets([controls, pointcache], forceElement=instance_node) - - def get_pre_create_attr_defs(self): - attrs = super(CreateRig, self).get_pre_create_attr_defs() - - return attrs + [ - BoolDef("fbx_enabled", - label="Fbx Export", - default=False), - - ] + skeleton = cmds.sets( + name=subset_name + "skeletonAnim_SET", empty=True) + skeleton_mesh = cmds.sets( + name=subset_name + "_skeletonMesh_SET", empty=True) + cmds.sets([controls, pointcache, + skeleton, skeleton_mesh], forceElement=instance_node) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py new file mode 100644 index 0000000000..e1b2fc0b7b --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from maya import cmds # noqa +import pyblish.api + + +class CollectFbxAnimation(pyblish.api.InstancePlugin): + """Collect Unreal Skeletal Mesh.""" + + order = pyblish.api.CollectorOrder + 0.2 + label = "Collect Fbx Animation" + families = ["rig"] + + def process(self, instance): + frame = cmds.currentTime(query=True) + instance.data["frameStart"] = frame + instance.data["frameEnd"] = frame + + skeleton_sets = [ + i for i in instance[:] + if i.lower().endswith("skeletonanim_set") + ] + if skeleton_sets: + for skeleton_set in skeleton_sets: + skeleton_content = cmds.ls( + cmds.sets(skeleton_set, query=True), long=True) + if skeleton_content: + instance.data["animated_skeleton"] += skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index bef43aa5f4..6ade7451d6 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -11,16 +11,12 @@ class CollectRigFbx(pyblish.api.InstancePlugin): families = ["rig"] def process(self, instance): - if not instance.data.get("fbx_enabled"): - self.log.debug("Skipping collecting rig data for fbx..") - return - frame = cmds.currentTime(query=True) instance.data["frameStart"] = frame instance.data["frameEnd"] = frame skeleton_sets = [ i for i in instance[:] - if i.lower().endswith("skeleton_set") + if i.lower().endswith("skeletonanim_set") ] skeleton_mesh_sets = [ @@ -28,14 +24,12 @@ class CollectRigFbx(pyblish.api.InstancePlugin): if i.lower().endswith("skeletonmesh_set") ] if skeleton_sets or skeleton_mesh_sets: - instance.data["geometries"] = [] - instance.data["control_rigs"] = [] instance.data["skeleton_mesh"] = [] for skeleton_set in skeleton_sets: skeleton_content = cmds.ls( cmds.sets(skeleton_set, query=True), long=True) if skeleton_content: - instance.data["control_rigs"] += skeleton_content + instance.data["animated_rigs"] += skeleton_content for skeleton_mesh_set in skeleton_mesh_sets: skeleton_mesh_content = cmds.ls( diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py new file mode 100644 index 0000000000..111a202f82 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +import os + +from maya import cmds # noqa +import maya.mel as mel # noqa +import pyblish.api + +from openpype.pipeline import publish +from openpype.pipeline.publish import OptionalPyblishPluginMixin +from openpype.hosts.maya.api import fbx + + +class ExtractRigFBX(publish.Extractor, + OptionalPyblishPluginMixin): + """Extract Rig in FBX format from Maya. + + This extracts the rig in fbx with the constraints + and referenced asset content included. + This also optionally extract animated rig in fbx with + geometries included. + + """ + order = pyblish.api.ExtractorOrder + label = "Extract Animation (FBX)" + families = ["animation"] + + def process(self, instance): + if not self.is_active(instance.data): + return + # Define output path + staging_dir = self.staging_dir(instance) + filename = "{0}.fbx".format(instance.name) + path = os.path.join(staging_dir, filename) + + # The export requires forward slashes because we need + # to format it into a string in a mel expression + fbx_exporter = fbx.FBXExtractor(log=self.log) + out_set = instance.data.get("animated_skeleton", []) + + instance.data["constraints"] = True + instance.data["animationOnly"] = True + + fbx_exporter.set_options_from_instance(instance) + + # Export + fbx_exporter.export(out_set, path) + + if "representations" not in instance.data: + instance.data["representations"] = [] + + representation = { + 'name': 'fbx', + 'ext': 'fbx', + 'files': filename, + "stagingDir": staging_dir, + "outputName": "fbxanim" + } + instance.data["representations"].append(representation) + + self.log.debug("Extract animated FBX successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index 687b686fb8..2aa02a21c3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -6,11 +6,12 @@ import maya.mel as mel # noqa import pyblish.api from openpype.pipeline import publish -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.pipeline.publish import OptionalPyblishPluginMixin from openpype.hosts.maya.api import fbx -class ExtractRigFBX(publish.Extractor): +class ExtractRigFBX(publish.Extractor, + OptionalPyblishPluginMixin): """Extract Rig in FBX format from Maya. This extracts the rig in fbx with the constraints @@ -24,11 +25,8 @@ class ExtractRigFBX(publish.Extractor): families = ["rig"] def process(self, instance): - if not instance.data.get("fbx_enabled"): - self.log.debug("fbx extractor has been disable.." - "Skipping the action...") + if not self.is_active(instance.data): return - # Define output path staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) @@ -36,14 +34,15 @@ class ExtractRigFBX(publish.Extractor): # The export requires forward slashes because we need # to format it into a string in a mel expression - path = path.replace('\\', '/') + fbx_exporter = fbx.FBXExtractor(log=self.log) + out_set = instance.data.get("skeleton_mesh", []) - self.log.debug("Extracting FBX to: {0}".format(path)) + instance.data["constraints"] = True - control_rigs = instance.data.get("control_rigs", []) - skeletal_mesh = instance.data.get("skeleton_mesh", []) - members = control_rigs + skeletal_mesh - self._to_extract(instance, path, members) + fbx_exporter.set_options_from_instance(instance) + + # Export + fbx_exporter.export(out_set, path) if "representations" not in instance.data: instance.data["representations"] = [] @@ -57,36 +56,3 @@ class ExtractRigFBX(publish.Extractor): instance.data["representations"].append(representation) self.log.debug("Extract FBX successful to: {0}".format(path)) - if skeletal_mesh: - self._to_extract(instance, path, skeletal_mesh) - representation = { - 'name': 'fbxanim', - 'ext': 'fbx', - 'files': filename, - "stagingDir": staging_dir, - "outputName": "fbxanim" - } - instance.data["representations"].append(representation) - self.log.debug( - "Extract animated FBX successful to: {0}".format(path)) - - def _to_extract(self, instance, path, members): - fbx_exporter = fbx.FBXExtractor(log=self.log) - control_rigs = instance.data.get("control_rigs", []) - skeletal_mesh = instance.data.get("skeleton_mesh", []) - static_sets = control_rigs + skeletal_mesh - if members == static_sets: - instance.data["constraints"] = True - instance.data["referencedAssetsContent"] = True - if members == skeletal_mesh: - instance.data["constraints"] = True - instance.data["referencedAssetsContent"] = True - instance.data["animationOnly"] = True - - fbx_exporter.set_options_from_instance(instance) - - # Export - with maintained_selection(): - fbx_exporter.export(members, path) - cmds.select(members, r=1, noExpand=True) - mel.eval('FBXExport -f "{}" -s'.format(path)) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index 7b5392f8f9..21d5097fd2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -80,6 +80,9 @@ class ValidateRigContents(pyblish.api.InstancePlugin): % invalid_geometry) error = True + invalid = self.validate_skeleton_sets(instance) + if invalid: + error = True if error: raise PublishValidationError( "Invalid rig content. See log for details.") @@ -91,7 +94,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): Checks if the node types of the set members valid Args: - set_members: list of nodes of the controls_set + set_members: list of nodes of the controls_SET hierarchy: list of nodes which reside under the root node Returns: @@ -118,7 +121,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): Checks if the node types of the set members valid Args: - set_members: list of nodes of the controls_set + set_members: list of nodes of the controls_SET hierarchy: list of nodes which reside under the root node Returns: @@ -132,3 +135,62 @@ class ValidateRigContents(pyblish.api.InstancePlugin): invalid.append(node) return invalid + + + def validate_skeleton_sets(self, instance): + objectsets = ("skeletonAnim_SET", "skeletonMesh_SET") + missing = [obj for obj in objectsets if obj not in instance] + if missing: + self.log.debug("%s is missing %s" % (instance, missing)) + + # Ensure there are at least some transforms or dag nodes + # in the rig instance + set_members = instance.data['setMembers'] + if not cmds.ls(set_members, type="dagNode", long=True): + self.log.debug("Skipping empty instance...") + return + # Ensure contents in sets and retrieve long path for all objects + output_content = cmds.sets( + "skeletonMesh_SET", query=True) or [] + output_content = cmds.ls(output_content, long=True) + + controls_content = cmds.sets( + "skeletonAnim_SET", query=True) or [] + controls_content = cmds.ls(controls_content, long=True) + + # Validate members are inside the hierarchy from root node + root_node = cmds.ls(set_members, assemblies=True) + hierarchy = cmds.listRelatives(root_node, allDescendents=True, + fullPath=True) + hierarchy = set(hierarchy) + + invalid_hierarchy = [] + if output_content: + for node in output_content: + if node not in hierarchy: + invalid_hierarchy.append(node) + invalid_geometry = self.validate_geometry(output_content) + if controls_content: + for node in controls_content: + if node not in hierarchy: + invalid_hierarchy.append(node) + invalid_controls = self.validate_controls(controls_content) + + error = False + if invalid_hierarchy: + self.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % invalid_hierarchy) + error = True + + if invalid_controls: + self.log.error("Only transforms can be part of the controls_SET." + "\n%s" % invalid_controls) + error = True + + if invalid_geometry: + self.log.error("Only meshes can be part of the out_SET\n%s" + % invalid_geometry) + error = True + + return error diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index 7bbf4257ab..ae9d9b51d2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -61,7 +61,10 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): controllers_sets = [i for i in instance if i == "controls_SET"] controls = cmds.sets(controllers_sets, query=True) assert controls, "Must have 'controls_SET' in rig instance" - + skeletonAnim_sets = [i for i in instance if i == "skeletonAnim_SET"] + if skeletonAnim_sets: + skeleton_controls = cmds.sets(skeletonAnim_sets, query=True) + controls += skeleton_controls # Ensure all controls are within the top group lookup = set(instance[:]) assert all(control in lookup for control in cmds.ls(controls, @@ -184,6 +187,9 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): # Use a single undo chunk with undo_chunk(): controls = cmds.sets("controls_SET", query=True) + anim_skeleton = cmds.sets("skeletonAnim_SET", query=True) + if anim_skeleton: + controls = controls + anim_skeleton for control in controls: # Lock visibility diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py index 842c1de01b..eae75089fc 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py @@ -56,7 +56,8 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - controllers_sets = [i for i in instance if i == "controls_SET"] + controllers_sets = [i for i in instance + if i == "controls_SET"] if not controllers_sets: return [] diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index 39f0941faa..05d7bfad64 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -47,7 +47,21 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): invalid = [] - out_set = next(x for x in instance if x.endswith("out_SET")) + out_set_invalid = cls.get_invalid_not_by_sets(instance) + if out_set_invalid: + invalid += out_set_invalid + + skeletonmesh_invalid = cls.get_invalid_not_by_sets( + instance, set_name="skeletonMesh_SET") + if skeletonmesh_invalid: + invalid += skeletonmesh_invalid + + return invalid + + @classmethod + def get_invalid_not_by_sets(cls, instance, set_name="out_SET"): + invalid = [] + out_set = next(x for x in instance if x.endswith(set_name)) members = cmds.sets(out_set, query=True) shapes = cmds.ls(members, dag=True, @@ -55,7 +69,8 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): shapes=True, long=True, noIntermediate=True) - + if not shapes: + return for shape in shapes: sibling_id = lib.get_id_from_sibling( shape, diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index cbc750bace..9e81b1223a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -40,17 +40,23 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance, compute=False): invalid_matches = cls.get_invalid_matches(instance, compute=compute) + + invalid_skeleton_matches = cls.get_invalid_matches( + instance, compute=compute, set_name="skeletonMesh_SET") + invalid_matches.update(invalid_skeleton_matches) return list(invalid_matches.keys()) @classmethod - def get_invalid_matches(cls, instance, compute=False): + def get_invalid_matches(cls, instance, compute=False, set_name="out_SET"): invalid = {} if compute: - out_set = next(x for x in instance if "out_SET" in x) + out_set = next(x for x in instance if set_name in x) instance_nodes = cmds.sets(out_set, query=True, nodesOnly=True) instance_nodes = cmds.ls(instance_nodes, long=True) + if not instance_nodes: + return for node in instance_nodes: shapes = cmds.listRelatives(node, shapes=True, fullPath=True) if shapes: From 2e36f7fc4723d27bbfcc40021a1e4b55051c3166 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 7 Sep 2023 20:48:19 +0800 Subject: [PATCH 04/59] hound --- openpype/hosts/maya/plugins/create/create_rig.py | 1 - openpype/hosts/maya/plugins/publish/validate_rig_contents.py | 1 - 2 files changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index b4ff6fad07..459fbdab56 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -1,7 +1,6 @@ from maya import cmds from openpype.hosts.maya.api import plugin -from openpype.lib import BoolDef class CreateRig(plugin.MayaCreator): diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index 21d5097fd2..276c22977e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -136,7 +136,6 @@ class ValidateRigContents(pyblish.api.InstancePlugin): return invalid - def validate_skeleton_sets(self, instance): objectsets = ("skeletonAnim_SET", "skeletonMesh_SET") missing = [obj for obj in objectsets if obj not in instance] From 9bbd457541071b729b048aea6215ce6f35448c0e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Sep 2023 17:16:33 +0800 Subject: [PATCH 05/59] big roy's comment on separating validators of skeleton set from the mandatory set --- .../plugins/publish/validate_rig_contents.py | 67 +----- .../publish/validate_rig_controllers.py | 24 +- ...idate_rig_controllers_arnold_attributes.py | 7 - .../publish/validate_rig_out_set_node_ids.py | 7 +- .../publish/validate_rig_output_ids.py | 19 +- .../publish/validate_skeleton_rig_content.py | 138 +++++++++++ .../validate_skeleton_rig_controller.py | 222 ++++++++++++++++++ .../validate_skeleton_rig_out_set_node_ids.py | 90 +++++++ .../validate_skeleton_rig_output_ids.py | 126 ++++++++++ 9 files changed, 581 insertions(+), 119 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py create mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py create mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py create mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index ad8a0a23c2..23f031a5db 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -96,9 +96,6 @@ class ValidateRigContents(pyblish.api.InstancePlugin): % invalid_geometry) error = True - invalid = self.validate_skeleton_sets(instance) - if invalid: - error = True if error: raise PublishValidationError( "Invalid rig content. See log for details.") @@ -110,7 +107,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): Checks if the node types of the set members valid Args: - set_members: list of nodes of the controls_SET + set_members: list of nodes of the controls_set hierarchy: list of nodes which reside under the root node Returns: @@ -137,7 +134,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): Checks if the node types of the set members valid Args: - set_members: list of nodes of the controls_SET + set_members: list of nodes of the controls_set hierarchy: list of nodes which reside under the root node Returns: @@ -151,63 +148,3 @@ class ValidateRigContents(pyblish.api.InstancePlugin): invalid.append(node) return invalid - - def validate_skeleton_sets(self, instance): - objectsets = ["skeletonAnim_SET", "skeletonMesh_SET"] - missing = [obj for obj in objectsets if obj not in instance] - if missing: - self.log.debug("%s is missing %s" % (instance, missing)) - - controls_set = instance.data["rig_sets"]["skeletonAnim_SET"] - out_set = instance.data["rig_sets"]["skeletonMesh_SET"] - # Ensure there are at least some transforms or dag nodes - # in the rig instance - set_members = instance.data['setMembers'] - if not cmds.ls(set_members, type="dagNode", long=True): - self.log.debug("Skipping empty instance...") - return - # Ensure contents in sets and retrieve long path for all objects - output_content = cmds.sets( - out_set, query=True) or [] - output_content = cmds.ls(output_content, long=True) - - controls_content = cmds.sets( - controls_set, query=True) or [] - controls_content = cmds.ls(controls_content, long=True) - - # Validate members are inside the hierarchy from root node - root_node = cmds.ls(set_members, assemblies=True) - hierarchy = cmds.listRelatives(root_node, allDescendents=True, - fullPath=True) - hierarchy = set(hierarchy) - - invalid_hierarchy = [] - if output_content: - for node in output_content: - if node not in hierarchy: - invalid_hierarchy.append(node) - invalid_geometry = self.validate_geometry(output_content) - if controls_content: - for node in controls_content: - if node not in hierarchy: - invalid_hierarchy.append(node) - invalid_controls = self.validate_controls(controls_content) - - error = False - if invalid_hierarchy: - self.log.error("Found nodes which reside outside of root group " - "while they are set up for publishing." - "\n%s" % invalid_hierarchy) - error = True - - if invalid_controls: - self.log.error("Only transforms can be part of the controls_SET." - "\n%s" % invalid_controls) - error = True - - if invalid_geometry: - self.log.error("Only meshes can be part of the out_SET\n%s" - % invalid_geometry) - error = True - - return error diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index 266d98a433..a3828f871b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -76,20 +76,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): "All controls must be inside the rig's group." ) return [controls_set] - skeleton_set = instance.data["rig_sets"].get("skeletonAnim_SET") - if not skeleton_set: - cls.log.info( - "No 'skeletonAnim_SET' in rig instance" - ) - skeleton_controls = cmds.sets(skeleton_set, query=True) - if not all(control in lookup for control in cmds.ls(skeleton_controls, - long=True)): - cls.log.error( - "All controls must be inside the rig's group." - ) - return [skeleton_controls] - controls += skeleton_controls # Validate all controls has_connections = list() has_unlocked_visibility = list() @@ -209,19 +196,10 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): "instance: {}".format(instance) ) return - skeleton_set = instance.data["rig_sets"].get("skeletonAnim_SET") - if not skeleton_set: - cls.log.error( - "Unable to repair because no 'skeletonAnim_SET' found in rig " - "instance: {}".format(instance) - ) - return + # Use a single undo chunk with undo_chunk(): controls = cmds.sets(controls_set, query=True) - if skeleton_set: - skeleton_controls = cmds.sets(skeleton_set, query=True) - controls += skeleton_controls for control in controls: # Lock visibility diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py index ec7fecf78a..03f6a5f1ab 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers_arnold_attributes.py @@ -63,13 +63,6 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): controls = cmds.sets(controls_set, query=True) or [] if not controls: return [] - skeleton_set = instance.data["rig_sets"].get("skeletonAnim_SET") - if not skeleton_set: - return [] - - skeleton_controls = cmds.sets(skeleton_set, query=True) or [] - if skeleton_controls: - controls += skeleton_controls shapes = cmds.ls(controls, dag=True, diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index a0d477b698..fbd510c683 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -49,10 +49,6 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): out_set = instance.data["rig_sets"].get("out_SET") if not out_set: return [] - skeletonMesh_set = instance.data["rig_sets"].get( - "skeletonMesh_SET") - if skeletonMesh_set: - out_set += skeletonMesh_set invalid = [] members = cmds.sets(out_set, query=True) @@ -62,8 +58,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): shapes=True, long=True, noIntermediate=True) - if not shapes: - return + for shape in shapes: sibling_id = lib.get_id_from_sibling( shape, diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index 5b3566a115..24fb36eb8b 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -40,14 +40,10 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance, compute=False): invalid_matches = cls.get_invalid_matches(instance, compute=compute) - - invalid_skeleton_matches = cls.get_invalid_matches( - instance, compute=compute, set_name="skeletonMesh_SET") - invalid_matches.update(invalid_skeleton_matches) return list(invalid_matches.keys()) @classmethod - def get_invalid_matches(cls, instance, compute=False, set_name="out_SET"): + def get_invalid_matches(cls, instance, compute=False): invalid = {} if compute: @@ -57,20 +53,7 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): return invalid instance_nodes = cmds.sets(out_set, query=True, nodesOnly=True) - - skeletonMesh_set = instance.data["rig_sets"].get( - "skeletonMesh_SET") - if not skeletonMesh_set: - instance.data["mismatched_output_ids"] = invalid - return invalid - else: - skeletonMesh_nodes = cmds.sets( - skeletonMesh_set, query=True, nodesOnly=True) - instance_nodes += skeletonMesh_nodes - instance_nodes = cmds.ls(instance_nodes, long=True) - if not instance_nodes: - return for node in instance_nodes: shapes = cmds.listRelatives(node, shapes=True, fullPath=True) if shapes: diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py new file mode 100644 index 0000000000..8e0a998a1d --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -0,0 +1,138 @@ +import pyblish.api +from maya import cmds + +from openpype.pipeline.publish import ( + PublishValidationError, + ValidateContentsOrder +) + + +class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): + """Ensure skeleton rigs contains pipeline-critical content + + The rigs optionally contain at least two object sets: + "skeletonAnim_SET" - Set of only bone hierarchies + "skeletonMesh_SET" - Set of all cacheable meshes + + """ + + order = ValidateContentsOrder + label = "Rig Contents" + hosts = ["maya"] + families = ["rig"] + + accepted_output = ["mesh", "transform"] + accepted_controllers = ["transform"] + + def process(self, instance): + + objectsets = ["skeletonAnim_SET", "skeletonMesh_SET"] + missing = [obj for obj in objectsets if obj not in instance] + if missing: + self.log.debug("%s is missing %s" % (instance, missing)) + return + + controls_set = instance.data["rig_sets"]["skeletonAnim_SET"] + out_set = instance.data["rig_sets"]["skeletonMesh_SET"] + # Ensure there are at least some transforms or dag nodes + # in the rig instance + set_members = instance.data['setMembers'] + if not cmds.ls(set_members, type="dagNode", long=True): + self.log.debug("Skipping empty instance...") + return + # Ensure contents in sets and retrieve long path for all objects + output_content = cmds.sets( + out_set, query=True) or [] + output_content = cmds.ls(output_content, long=True) + + controls_content = cmds.sets( + controls_set, query=True) or [] + controls_content = cmds.ls(controls_content, long=True) + + # Validate members are inside the hierarchy from root node + root_node = cmds.ls(set_members, assemblies=True) + hierarchy = cmds.listRelatives(root_node, allDescendents=True, + fullPath=True) + hierarchy = set(hierarchy) + + invalid_hierarchy = [] + if output_content: + for node in output_content: + if node not in hierarchy: + invalid_hierarchy.append(node) + invalid_geometry = self.validate_geometry(output_content) + if controls_content: + for node in controls_content: + if node not in hierarchy: + invalid_hierarchy.append(node) + invalid_controls = self.validate_controls(controls_content) + + error = False + if invalid_hierarchy: + self.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % invalid_hierarchy) + error = True + + if invalid_controls: + self.log.error("Only transforms can be part of the skeletonAnim_SET." + "\n%s" % invalid_controls) + error = True + + if invalid_geometry: + self.log.error("Only meshes can be part of the skeletonMesh_SET\n%s" + % invalid_geometry) + error = True + + if error: + raise PublishValidationError( + "Invalid rig content. See log for details.") + + def validate_geometry(self, set_members): + """Check if the out set passes the validations + + Checks if all its set members are within the hierarchy of the root + Checks if the node types of the set members valid + + Args: + set_members: list of nodes of the controls_SET + hierarchy: list of nodes which reside under the root node + + Returns: + errors (list) + """ + + # Validate all shape types + invalid = [] + shapes = cmds.listRelatives(set_members, + allDescendents=True, + shapes=True, + fullPath=True) or [] + all_shapes = cmds.ls(set_members + shapes, long=True, shapes=True) + for shape in all_shapes: + if cmds.nodeType(shape) not in self.accepted_output: + invalid.append(shape) + + return invalid + + def validate_controls(self, set_members): + """Check if the controller set passes the validations + + Checks if all its set members are within the hierarchy of the root + Checks if the node types of the set members valid + + Args: + set_members: list of nodes of the controls_SET + hierarchy: list of nodes which reside under the root node + + Returns: + errors (list) + """ + + # Validate control types + invalid = [] + for node in set_members: + if cmds.nodeType(node) not in self.accepted_controllers: + invalid.append(node) + + return invalid diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py new file mode 100644 index 0000000000..82e0d542ca --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py @@ -0,0 +1,222 @@ +from maya import cmds + +import pyblish.api + +from openpype.pipeline.publish import ( + ValidateContentsOrder, + RepairAction, + PublishValidationError +) +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api.lib import undo_chunk + + +class ValidateSkeletonRigControllers(pyblish.api.InstancePlugin): + """Validate rig controller for skeletonAnim_SET + + Controls must have the transformation attributes on their default + values of translate zero, rotate zero and scale one when they are + unlocked attributes. + + Unlocked keyable attributes may not have any incoming connections. If + these connections are required for the rig then lock the attributes. + + The visibility attribute must be locked. + + Note that `repair` will: + - Lock all visibility attributes + - Reset all default values for translate, rotate, scale + - Break all incoming connections to keyable attributes + + """ + order = ValidateContentsOrder + 0.05 + label = "Rig Controllers" + hosts = ["maya"] + families = ["rig"] + actions = [RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] + + # Default controller values + CONTROLLER_DEFAULTS = { + "translateX": 0, + "translateY": 0, + "translateZ": 0, + "rotateX": 0, + "rotateY": 0, + "rotateZ": 0, + "scaleX": 1, + "scaleY": 1, + "scaleZ": 1 + } + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError( + '{} failed, see log information'.format(self.label) + ) + + @classmethod + def get_invalid(cls, instance): + skeleton_set = instance.data["rig_sets"].get("skeletonAnim_SET") + if not skeleton_set: + cls.log.info( + "No 'skeletonAnim_SET' in rig instance" + ) + return + controls = cmds.sets(skeleton_set, query=True) + lookup = set(instance[:]) + if not all(control in lookup for control in cmds.ls(controls, + long=True)): + cls.log.error( + "All controls must be inside the rig's group." + ) + return [controls] + # Validate all controls + has_connections = list() + has_unlocked_visibility = list() + has_non_default_values = list() + for control in controls: + if cls.get_connected_attributes(control): + has_connections.append(control) + + # check if visibility is locked + attribute = "{}.visibility".format(control) + locked = cmds.getAttr(attribute, lock=True) + if not locked: + has_unlocked_visibility.append(control) + + if cls.get_non_default_attributes(control): + has_non_default_values.append(control) + + if has_connections: + cls.log.error("Controls have input connections: " + "%s" % has_connections) + + if has_non_default_values: + cls.log.error("Controls have non-default values: " + "%s" % has_non_default_values) + + if has_unlocked_visibility: + cls.log.error("Controls have unlocked visibility " + "attribute: %s" % has_unlocked_visibility) + + invalid = [] + if (has_connections or + has_unlocked_visibility or + has_non_default_values): + invalid = set() + invalid.update(has_connections) + invalid.update(has_non_default_values) + invalid.update(has_unlocked_visibility) + invalid = list(invalid) + cls.log.error("Invalid rig controllers. See log for details.") + + return invalid + + @classmethod + def get_non_default_attributes(cls, control): + """Return attribute plugs with non-default values + + Args: + control (str): Name of control node. + + Returns: + list: The invalid plugs + + """ + + invalid = [] + for attr, default in cls.CONTROLLER_DEFAULTS.items(): + if cmds.attributeQuery(attr, node=control, exists=True): + plug = "{}.{}".format(control, attr) + + # Ignore locked attributes + locked = cmds.getAttr(plug, lock=True) + if locked: + continue + + value = cmds.getAttr(plug) + if value != default: + cls.log.warning("Control non-default value: " + "%s = %s" % (plug, value)) + invalid.append(plug) + + return invalid + + @staticmethod + def get_connected_attributes(control): + """Return attribute plugs with incoming connections. + + This will also ensure no (driven) keys on unlocked keyable attributes. + + Args: + control (str): Name of control node. + + Returns: + list: The invalid plugs + + """ + import maya.cmds as mc + + # Support controls without any attributes returning None + attributes = mc.listAttr(control, keyable=True, scalar=True) or [] + invalid = [] + for attr in attributes: + plug = "{}.{}".format(control, attr) + + # Ignore locked attributes + locked = cmds.getAttr(plug, lock=True) + if locked: + continue + + # Ignore proxy connections. + if (cmds.addAttr(plug, query=True, exists=True) and + cmds.addAttr(plug, query=True, usedAsProxy=True)): + continue + + # Check for incoming connections + if cmds.listConnections(plug, source=True, destination=False): + invalid.append(plug) + + return invalid + + @classmethod + def repair(cls, instance): + skeleton_set = instance.data["rig_sets"].get("skeletonAnim_SET") + if not skeleton_set: + cls.log.error( + "Unable to repair because no 'skeletonAnim_SET' found in rig " + "instance: {}".format(instance) + ) + return + # Use a single undo chunk + with undo_chunk(): + controls = cmds.sets(skeleton_set, query=True) + for control in controls: + # Lock visibility + attr = "{}.visibility".format(control) + locked = cmds.getAttr(attr, lock=True) + if not locked: + cls.log.info("Locking visibility for %s" % control) + cmds.setAttr(attr, lock=True) + + # Remove incoming connections + invalid_plugs = cls.get_connected_attributes(control) + if invalid_plugs: + for plug in invalid_plugs: + cls.log.info("Breaking input connection to %s" % plug) + source = cmds.listConnections(plug, + source=True, + destination=False, + plugs=True)[0] + cmds.disconnectAttr(source, plug) + + # Reset non-default values + invalid_plugs = cls.get_non_default_attributes(control) + if invalid_plugs: + for plug in invalid_plugs: + attr = plug.split(".")[-1] + default = cls.CONTROLLER_DEFAULTS[attr] + cls.log.info("Setting %s to %s" % (plug, default)) + cmds.setAttr(plug, default) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py new file mode 100644 index 0000000000..b682c8e953 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py @@ -0,0 +1,90 @@ +import maya.cmds as cmds + +import pyblish.api + +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api import lib +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError +) + + +class ValidateSkeletonRigOutSetNodeIds(pyblish.api.InstancePlugin): + """Validate if deformed shapes have related IDs to the original shapes + from skeleton set. + + When a deformer is applied in the scene on a referenced mesh that already + had deformers then Maya will create a new shape node for the mesh that + does not have the original id. This validator checks whether the ids are + valid on all the shape nodes in the instance. + + """ + + order = ValidateContentsOrder + families = ["rig"] + hosts = ['maya'] + label = 'Rig Out Set Node Ids' + actions = [ + openpype.hosts.maya.api.action.SelectInvalidAction, + RepairAction + ] + allow_history_only = False + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId and a related ID to the original shapes + # if a deformer has been created on the shape + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError( + "Nodes found with mismatching IDs: {0}".format(invalid) + ) + + @classmethod + def get_invalid(cls, instance): + """Get all nodes which do not match the criteria""" + + skeletonMesh_set = instance.data["rig_sets"].get( + "skeletonMesh_SET") + if not skeletonMesh_set: + return [] + + invalid = [] + members = cmds.sets(skeletonMesh_set, query=True) + shapes = cmds.ls(members, + dag=True, + leaf=True, + shapes=True, + long=True, + noIntermediate=True) + if not shapes: + return + for shape in shapes: + sibling_id = lib.get_id_from_sibling( + shape, + history_only=cls.allow_history_only + ) + if sibling_id: + current_id = lib.get_id(shape) + if current_id != sibling_id: + invalid.append(shape) + + return invalid + + @classmethod + def repair(cls, instance): + + for node in cls.get_invalid(instance): + # Get the original id from sibling + sibling_id = lib.get_id_from_sibling( + node, + history_only=cls.allow_history_only + ) + if not sibling_id: + cls.log.error("Could not find ID in siblings for '%s'", node) + continue + + lib.set_id(node, sibling_id, overwrite=True) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py new file mode 100644 index 0000000000..76f058a94b --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py @@ -0,0 +1,126 @@ +from collections import defaultdict + +from maya import cmds + +import pyblish.api + +import openpype.hosts.maya.api.action +from openpype.hosts.maya.api.lib import get_id, set_id +from openpype.pipeline.publish import ( + RepairAction, + ValidateContentsOrder, + PublishValidationError +) + + +def get_basename(node): + """Return node short name without namespace""" + return node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + + +class ValidateSkeletonRigOutputIds(pyblish.api.InstancePlugin): + """Validate rig output ids from the skeleton sets. + + Ids must share the same id as similarly named nodes in the scene. This is + to ensure the id from the model is preserved through animation. + + """ + order = ValidateContentsOrder + 0.05 + label = "Rig Output Ids" + hosts = ["maya"] + families = ["rig"] + actions = [RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] + + def process(self, instance): + invalid = self.get_invalid(instance, compute=True) + if invalid: + raise PublishValidationError("Found nodes with mismatched IDs.") + + @classmethod + def get_invalid(cls, instance, compute=False): + invalid_matches = cls.get_invalid_matches(instance, compute=compute) + + invalid_skeleton_matches = cls.get_invalid_matches( + instance, compute=compute, set_name="skeletonMesh_SET") + invalid_matches.update(invalid_skeleton_matches) + return list(invalid_matches.keys()) + + @classmethod + def get_invalid_matches(cls, instance, compute=False): + invalid = {} + + if compute: + skeletonMesh_set = instance.data["rig_sets"].get( + "skeletonMesh_SET") + if not skeletonMesh_set: + instance.data["mismatched_output_ids"] = invalid + return invalid + + instance_nodes = cmds.sets( + skeletonMesh_set, query=True, nodesOnly=True) + + instance_nodes = cmds.ls(instance_nodes, long=True) + if not instance_nodes: + return + for node in instance_nodes: + shapes = cmds.listRelatives(node, shapes=True, fullPath=True) + if shapes: + instance_nodes.extend(shapes) + + scene_nodes = cmds.ls(type="transform", long=True) + scene_nodes += cmds.ls(type="mesh", long=True) + scene_nodes = set(scene_nodes) - set(instance_nodes) + + scene_nodes_by_basename = defaultdict(list) + for node in scene_nodes: + basename = get_basename(node) + scene_nodes_by_basename[basename].append(node) + + for instance_node in instance_nodes: + basename = get_basename(instance_node) + if basename not in scene_nodes_by_basename: + continue + + matches = scene_nodes_by_basename[basename] + + ids = set(get_id(node) for node in matches) + ids.add(get_id(instance_node)) + + if len(ids) > 1: + cls.log.error( + "\"{}\" id mismatch to: {}".format( + instance_node, matches + ) + ) + invalid[instance_node] = matches + + instance.data["mismatched_output_ids"] = invalid + else: + invalid = instance.data["mismatched_output_ids"] + + return invalid + + @classmethod + def repair(cls, instance): + invalid_matches = cls.get_invalid_matches(instance) + + multiple_ids_match = [] + for instance_node, matches in invalid_matches.items(): + ids = set(get_id(node) for node in matches) + + # If there are multiple scene ids matched, and error needs to be + # raised for manual correction. + if len(ids) > 1: + multiple_ids_match.append({"node": instance_node, + "matches": matches}) + continue + + id_to_set = next(iter(ids)) + set_id(instance_node, id_to_set, overwrite=True) + + if multiple_ids_match: + raise PublishValidationError( + "Multiple matched ids found. Please repair manually: " + "{}".format(multiple_ids_match) + ) From 35e287a57d99bb04aa25ca5f06048b8326cc618e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Sep 2023 17:19:32 +0800 Subject: [PATCH 06/59] big roy's comment --- .../plugins/publish/validate_skeleton_rig_out_set_node_ids.py | 2 +- .../maya/plugins/publish/validate_skeleton_rig_output_ids.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py index b682c8e953..d62bd68b15 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py @@ -61,7 +61,7 @@ class ValidateSkeletonRigOutSetNodeIds(pyblish.api.InstancePlugin): long=True, noIntermediate=True) if not shapes: - return + return [] for shape in shapes: sibling_id = lib.get_id_from_sibling( shape, diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py index 76f058a94b..bea18977f3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py @@ -62,7 +62,7 @@ class ValidateSkeletonRigOutputIds(pyblish.api.InstancePlugin): instance_nodes = cmds.ls(instance_nodes, long=True) if not instance_nodes: - return + return {} for node in instance_nodes: shapes = cmds.listRelatives(node, shapes=True, fullPath=True) if shapes: From cce6bf2e4299f61118eb5ca479f141e50704be9e Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Sep 2023 17:24:01 +0800 Subject: [PATCH 07/59] hound --- .../maya/plugins/publish/validate_skeleton_rig_content.py | 8 ++++---- .../plugins/publish/validate_skeleton_rig_output_ids.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index 8e0a998a1d..b70d8e6f3f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -75,13 +75,13 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): error = True if invalid_controls: - self.log.error("Only transforms can be part of the skeletonAnim_SET." - "\n%s" % invalid_controls) + self.log.error("Only transforms can be part of the " + "skeletonAnim_SET. \n%s" % invalid_controls) error = True if invalid_geometry: - self.log.error("Only meshes can be part of the skeletonMesh_SET\n%s" - % invalid_geometry) + self.log.error("Only meshes can be part of the " + "skeletonMesh_SET\n%s" % invalid_geometry) error = True if error: diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py index bea18977f3..0b936d35f4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py @@ -58,7 +58,7 @@ class ValidateSkeletonRigOutputIds(pyblish.api.InstancePlugin): return invalid instance_nodes = cmds.sets( - skeletonMesh_set, query=True, nodesOnly=True) + skeletonMesh_set, query=True, nodesOnly=True) instance_nodes = cmds.ls(instance_nodes, long=True) if not instance_nodes: From cfb4ceb5d41ea59f587555f748da902bae46dc64 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Sep 2023 17:34:45 +0800 Subject: [PATCH 08/59] big roy's comment on rig.fbx families --- .../maya/plugins/publish/collect_fbx_animation.py | 1 + .../maya/plugins/publish/collect_rig_for_fbx.py | 1 + .../maya/plugins/publish/extract_fbx_animation.py | 2 +- .../hosts/maya/plugins/publish/extract_rig_fbx.py | 2 +- .../plugins/publish/validate_skeleton_rig_content.py | 12 ++++++++---- .../publish/validate_skeleton_rig_controller.py | 4 ++-- .../validate_skeleton_rig_out_set_node_ids.py | 4 ++-- .../publish/validate_skeleton_rig_output_ids.py | 4 ++-- openpype/plugins/publish/integrate.py | 1 + 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index e1b2fc0b7b..fb045973b6 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -20,6 +20,7 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin): if i.lower().endswith("skeletonanim_set") ] if skeleton_sets: + instance.data["families"].append("rig.fbx") for skeleton_set in skeleton_sets: skeleton_content = cmds.ls( cmds.sets(skeleton_set, query=True), long=True) diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index 6ade7451d6..853dcbb259 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -24,6 +24,7 @@ class CollectRigFbx(pyblish.api.InstancePlugin): if i.lower().endswith("skeletonmesh_set") ] if skeleton_sets or skeleton_mesh_sets: + instance.data["families"].append("rig.fbx") instance.data["skeleton_mesh"] = [] for skeleton_set in skeleton_sets: skeleton_content = cmds.ls( diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 111a202f82..8c540a0101 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -22,7 +22,7 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Animation (FBX)" - families = ["animation"] + families = ["rig.fbx"] def process(self, instance): if not self.is_active(instance.data): diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index 2aa02a21c3..ebaf8a83ca 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -22,7 +22,7 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Rig (FBX)" - families = ["rig"] + families = ["rig.fbx"] def process(self, instance): if not self.is_active(instance.data): diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index b70d8e6f3f..0406b00ec6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -17,9 +17,9 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder - label = "Rig Contents" + label = "Skeleton Rig Contents" hosts = ["maya"] - families = ["rig"] + families = ["rig.fbx"] accepted_output = ["mesh", "transform"] accepted_controllers = ["transform"] @@ -27,9 +27,13 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): def process(self, instance): objectsets = ["skeletonAnim_SET", "skeletonMesh_SET"] - missing = [obj for obj in objectsets if obj not in instance] + missing = [ + key for key in objectsets if key not in instance.data["rig_sets"] + ] if missing: - self.log.debug("%s is missing %s" % (instance, missing)) + self.log.debug( + "%s is missing sets: %s" % (instance, ", ".join(missing)) + ) return controls_set = instance.data["rig_sets"]["skeletonAnim_SET"] diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py index 82e0d542ca..a31d13bcec 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py @@ -30,9 +30,9 @@ class ValidateSkeletonRigControllers(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder + 0.05 - label = "Rig Controllers" + label = "Skeleton Rig Controllers" hosts = ["maya"] - families = ["rig"] + families = ["rig.fbx"] actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py index d62bd68b15..73ad12f422 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py @@ -23,9 +23,9 @@ class ValidateSkeletonRigOutSetNodeIds(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder - families = ["rig"] + families = ["rig.fbx"] hosts = ['maya'] - label = 'Rig Out Set Node Ids' + label = 'Skeleton Rig Out Set Node Ids' actions = [ openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py index 0b936d35f4..0d1e702749 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py @@ -26,9 +26,9 @@ class ValidateSkeletonRigOutputIds(pyblish.api.InstancePlugin): """ order = ValidateContentsOrder + 0.05 - label = "Rig Output Ids" + label = "Skeleton Rig Output Ids" hosts = ["maya"] - families = ["rig"] + families = ["rig.fbx"] actions = [RepairAction, openpype.hosts.maya.api.action.SelectInvalidAction] diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 7e48155b9e..2e122b652e 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -105,6 +105,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "review", "rendersetup", "rig", + "rig.fbx", "plate", "look", "audio", From 3b6079f74374659a38bfc9b725cabbf26858b05f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 8 Sep 2023 17:58:38 +0800 Subject: [PATCH 09/59] remove rig.fbx --- openpype/plugins/publish/integrate.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 2e122b652e..7e48155b9e 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -105,7 +105,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "review", "rendersetup", "rig", - "rig.fbx", "plate", "look", "audio", From 5c3f12d51897b4522e9ce3a364e6aa3c71963a6d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 11 Sep 2023 20:19:40 +0800 Subject: [PATCH 10/59] make the validators optional --- .../defaults/project_settings/maya.json | 20 +++++++++ .../schemas/schema_maya_publish.json | 44 +++++++++++++++++-- .../maya/server/settings/publishers.py | 36 +++++++++++++++ server_addon/maya/server/version.py | 2 +- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 38f14ec022..2bc226c431 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1140,6 +1140,16 @@ "optional": false, "active": true }, + "ValidateSkeletonRigContents": { + "enabled": false, + "optional": true, + "active": true + }, + "ValidateSkeletonRigControllers": { + "enabled": false, + "optional": true, + "active": true + }, "ValidateSkinclusterDeformerSet": { "enabled": true, "optional": false, @@ -1150,6 +1160,16 @@ "optional": false, "allow_history_only": false }, + "ValidateSkeletonRigOutSetNodeIds": { + "enabled": false, + "optional": false, + "allow_history_only": false + }, + "ValidateSkeletonRigOutputIds": { + "enabled": false, + "optional": true, + "active": true + }, "ValidateCameraAttributes": { "enabled": false, "optional": true, 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 b115ee3faa..e8300282d7 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 @@ -809,9 +809,47 @@ "key": "ValidateSkeletalMeshHierarchy", "label": "Validate Skeletal Mesh Top Node" }, - { + { + "key": "ValidateSkeletonRigContents", + "label": "ValidateSkeleton Rig Contents" + }, + { + "key": "ValidateSkeletonRigControllers", + "label": "Validate Skeleton Rig Controllers" + }, + { "key": "ValidateSkinclusterDeformerSet", "label": "Validate Skincluster Deformer Relationships" + }, + { + "key": "ValidateSkeletonRigOutputIds", + "label": "Validate Skeleton Rig Output Ids" + } + ] + }, + + { + "type": "dict", + "collapsible": true, + "checkbox_key": "enabled", + "key": "ValidateRigOutSetNodeIds", + "label": "Validate Rig Out Set Node Ids", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, + { + "type": "boolean", + "key": "allow_history_only", + "label": "Allow history only" } ] }, @@ -819,8 +857,8 @@ "type": "dict", "collapsible": true, "checkbox_key": "enabled", - "key": "ValidateRigOutSetNodeIds", - "label": "Validate Rig Out Set Node Ids", + "key": "ValidateSkeletonRigOutSetNodeIds", + "label": "Validate Skeleton Rig Out Set Node Ids", "is_group": true, "children": [ { diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index bd7ccdf4d5..6e3179b78e 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -660,14 +660,30 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Skeletal Mesh Top Node", ) + ValidateSkeletonRigContents: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Skeleton Rig Contents" + ) + ValidateSkeletonRigControllers: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Skeleton Rig Controllers" + ) ValidateSkinclusterDeformerSet: BasicValidateModel = Field( default_factory=BasicValidateModel, title="Validate Skincluster Deformer Relationships", ) + ValidateSkeletonRigOutputIds: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Skeleton Rig Output Ids" + ) ValidateRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field( default_factory=ValidateRigOutSetNodeIdsModel, title="Validate Rig Out Set Node Ids", ) + ValidateSkeletonRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field( + default_factory=ValidateRigOutSetNodeIdsModel, + title="Validate Skeleton Rig Out Set Node Ids", + ) # Rig - END ValidateCameraAttributes: BasicValidateModel = Field( default_factory=BasicValidateModel, @@ -1163,6 +1179,16 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": False, "active": True }, + "ValidateSkeletonRigContents": { + "enabled": False, + "optional": True, + "active": True + }, + "ValidateSkeletonRigControllers": { + "enabled": False, + "optional": True, + "active": True + }, "ValidateSkinclusterDeformerSet": { "enabled": True, "optional": False, @@ -1173,6 +1199,16 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": False, "allow_history_only": False }, + "ValidateSkeletonRigOutSetNodeIds": { + "enabled": False, + "optional": False, + "allow_history_only": False + }, + "ValidateSkeletonRigOutputIds": { + "enabled": False, + "optional": True, + "active": True + }, "ValidateCameraAttributes": { "enabled": False, "optional": True, diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py index e57ad00718..de699158fd 100644 --- a/server_addon/maya/server/version.py +++ b/server_addon/maya/server/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring addon version.""" -__version__ = "0.1.3" +__version__ = "0.1.4" From 09e797577b78650eb31e464311c0478564b8f130 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 11 Sep 2023 21:35:58 +0800 Subject: [PATCH 11/59] remove the irrelevant fbx output parameter --- openpype/hosts/maya/api/fbx.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 064ba00f08..000c723d37 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -64,8 +64,7 @@ class FBXExtractor: "embeddedTextures": bool, "inputConnections": bool, "upAxis": str, # x, y or z, - "triangulate": bool, - "exportFileVersion": str + "triangulate": bool } @property @@ -106,8 +105,7 @@ class FBXExtractor: "embeddedTextures": False, "inputConnections": True, "upAxis": "y", - "triangulate": False, - "exportFileVersion": "FBX201000" + "triangulate": False } def __init__(self, log=None): From 5506a89ad644e348be3b8fbb08714496b64f64f2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 11 Sep 2023 21:40:38 +0800 Subject: [PATCH 12/59] wrong family for collect fbx animation --- openpype/hosts/maya/plugins/publish/collect_fbx_animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index fb045973b6..a9a13637f7 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -8,7 +8,7 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.2 label = "Collect Fbx Animation" - families = ["rig"] + families = ["animation"] def process(self, instance): frame = cmds.currentTime(query=True) From 6f23755873fe66e69d549f4551717509a500c75f Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Sep 2023 19:48:36 +0800 Subject: [PATCH 13/59] make sure the rig family has published fbx and file version should be 2022 --- openpype/hosts/maya/api/fbx.py | 6 +++-- .../hosts/maya/plugins/create/create_rig.py | 2 +- .../plugins/publish/collect_fbx_animation.py | 8 +++---- .../plugins/publish/collect_rig_for_fbx.py | 23 +++++++++++-------- .../plugins/publish/extract_fbx_animation.py | 8 +++++-- .../maya/plugins/publish/extract_rig_fbx.py | 12 +++++++--- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 000c723d37..18b28f5154 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -64,7 +64,8 @@ class FBXExtractor: "embeddedTextures": bool, "inputConnections": bool, "upAxis": str, # x, y or z, - "triangulate": bool + "triangulate": bool, + "FileVersion": str } @property @@ -105,7 +106,8 @@ class FBXExtractor: "embeddedTextures": False, "inputConnections": True, "upAxis": "y", - "triangulate": False + "triangulate": False, + "fileVersion": "FBX202000" } def __init__(self, log=None): diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 459fbdab56..69c7787905 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -25,7 +25,7 @@ class CreateRig(plugin.MayaCreator): # change name (_out_SET -> _geo_SET) pointcache = cmds.sets(name=subset_name + "_out_SET", empty=True) skeleton = cmds.sets( - name=subset_name + "skeletonAnim_SET", empty=True) + name=subset_name + "_skeletonAnim_SET", empty=True) skeleton_mesh = cmds.sets( name=subset_name + "_skeletonMesh_SET", empty=True) cmds.sets([controls, pointcache, diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index a9a13637f7..aef838223e 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -16,13 +16,13 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin): instance.data["frameEnd"] = frame skeleton_sets = [ - i for i in instance[:] + i for i in instance if i.lower().endswith("skeletonanim_set") ] if skeleton_sets: - instance.data["families"].append("rig.fbx") + instance.data["families"].append("animation.fbx") for skeleton_set in skeleton_sets: - skeleton_content = cmds.ls( - cmds.sets(skeleton_set, query=True), long=True) + skeleton_content = cmds.sets(skeleton_set, query=True) + self.log.debug(f"Collected Animated Skeleton Set: {skeleton_content}") if skeleton_content: instance.data["animated_skeleton"] += skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index 853dcbb259..fe8d5ca8ef 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -15,25 +15,28 @@ class CollectRigFbx(pyblish.api.InstancePlugin): instance.data["frameStart"] = frame instance.data["frameEnd"] = frame skeleton_sets = [ - i for i in instance[:] + i for i in instance if i.lower().endswith("skeletonanim_set") ] skeleton_mesh_sets = [ - i for i in instance[:] + i for i in instance if i.lower().endswith("skeletonmesh_set") ] - if skeleton_sets or skeleton_mesh_sets: - instance.data["families"].append("rig.fbx") - instance.data["skeleton_mesh"] = [] + if not skeleton_sets and skeleton_mesh_sets: + self.log.debug("no skeleton_set or skeleton_mesh set was found....") + return + instance.data["skeleton_mesh"] = [] + if skeleton_sets: for skeleton_set in skeleton_sets: - skeleton_content = cmds.ls( - cmds.sets(skeleton_set, query=True), long=True) + skeleton_content = cmds.sets(skeleton_set, query=True) if skeleton_content: instance.data["animated_rigs"] += skeleton_content - + self.log.debug(f"Collected Skeleton Set: {skeleton_content}") + if skeleton_mesh_sets: + instance.data["families"].append("rig.fbx") for skeleton_mesh_set in skeleton_mesh_sets: - skeleton_mesh_content = cmds.ls( - cmds.sets(skeleton_mesh_set, query=True), long=True) + skeleton_mesh_content = cmds.sets(skeleton_mesh_set, query=True) if skeleton_mesh_content: instance.data["skeleton_mesh"] += skeleton_mesh_content + self.log.debug(f"Collected SkeletonMesh Set: {skeleton_mesh_content}") diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 8c540a0101..2ac4734d21 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -2,7 +2,6 @@ import os from maya import cmds # noqa -import maya.mel as mel # noqa import pyblish.api from openpype.pipeline import publish @@ -22,11 +21,16 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Animation (FBX)" - families = ["rig.fbx"] + families = ["animation"] def process(self, instance): if not self.is_active(instance.data): return + if "animation.fbx" not in instance.data["families"]: + self.log.debug("No object inside skeleton_set..Skipping...") + return + if not cmds.loadPlugin("fbxmaya", query=True): + cmds.loadPlugin("fbxmaya", quiet=True) # Define output path staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index ebaf8a83ca..0df602fa29 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -2,7 +2,6 @@ import os from maya import cmds # noqa -import maya.mel as mel # noqa import pyblish.api from openpype.pipeline import publish @@ -22,12 +21,17 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Rig (FBX)" - families = ["rig.fbx"] + families = ["rig"] def process(self, instance): if not self.is_active(instance.data): return - # Define output path + if "rig.fbx" not in instance.data["families"]: + self.log.debug("No object inside skeletonMesh_set..Skipping..") + return + if not cmds.loadPlugin("fbxmaya", query=True): + cmds.loadPlugin("fbxmaya", quiet=True) + staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) path = os.path.join(staging_dir, filename) @@ -46,6 +50,7 @@ class ExtractRigFBX(publish.Extractor, if "representations" not in instance.data: instance.data["representations"] = [] + self.log.debug("Families: {}".format(instance.data["families"])) representation = { 'name': 'fbx', @@ -54,5 +59,6 @@ class ExtractRigFBX(publish.Extractor, "stagingDir": staging_dir, } instance.data["representations"].append(representation) + self.log.debug("Representation: {}".format(representation)) self.log.debug("Extract FBX successful to: {0}".format(path)) From 8b3e2259be7cd7d308804fd964a337d20c73c961 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 12 Sep 2023 21:34:49 +0800 Subject: [PATCH 14/59] hound --- .../maya/plugins/publish/collect_fbx_animation.py | 4 +++- .../maya/plugins/publish/collect_rig_for_fbx.py | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index aef838223e..72501dc819 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -23,6 +23,8 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin): instance.data["families"].append("animation.fbx") for skeleton_set in skeleton_sets: skeleton_content = cmds.sets(skeleton_set, query=True) - self.log.debug(f"Collected Animated Skeleton Set: {skeleton_content}") + self.log.debug( + "Collected animated " + f"skeleton data: {skeleton_content}") if skeleton_content: instance.data["animated_skeleton"] += skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index fe8d5ca8ef..d571975438 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -24,7 +24,8 @@ class CollectRigFbx(pyblish.api.InstancePlugin): if i.lower().endswith("skeletonmesh_set") ] if not skeleton_sets and skeleton_mesh_sets: - self.log.debug("no skeleton_set or skeleton_mesh set was found....") + self.log.debug( + "no skeleton_set or skeleton_mesh set was found....") return instance.data["skeleton_mesh"] = [] if skeleton_sets: @@ -32,11 +33,14 @@ class CollectRigFbx(pyblish.api.InstancePlugin): skeleton_content = cmds.sets(skeleton_set, query=True) if skeleton_content: instance.data["animated_rigs"] += skeleton_content - self.log.debug(f"Collected Skeleton Set: {skeleton_content}") + self.log.debug(f"Collected skeleton data: {skeleton_content}") if skeleton_mesh_sets: instance.data["families"].append("rig.fbx") for skeleton_mesh_set in skeleton_mesh_sets: - skeleton_mesh_content = cmds.sets(skeleton_mesh_set, query=True) + skeleton_mesh_content = cmds.sets( + skeleton_mesh_set, query=True) if skeleton_mesh_content: instance.data["skeleton_mesh"] += skeleton_mesh_content - self.log.debug(f"Collected SkeletonMesh Set: {skeleton_mesh_content}") + self.log.debug( + "Collected skeleton " + f"mesh Set: {skeleton_mesh_content}") From e64984d510d84afb238df73296d8320cb3de2f3a Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Sep 2023 13:34:44 +0800 Subject: [PATCH 15/59] update fbx param to support skeleton definition exports and add the optional validators to make sure it's always a top group hierarchy of the rig in the sets --- openpype/hosts/maya/api/fbx.py | 8 +-- .../plugins/publish/collect_rig_for_fbx.py | 3 +- .../plugins/publish/extract_fbx_animation.py | 1 + .../maya/plugins/publish/extract_rig_fbx.py | 3 +- .../validate_skeleton_top_group_hierarchy.py | 49 +++++++++++++++++++ .../schemas/schema_maya_publish.json | 4 ++ .../maya/server/settings/publishers.py | 9 ++++ 7 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 18b28f5154..306b7efe0b 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -40,7 +40,7 @@ class FBXExtractor: the option is not included and a warning is logged. """ - + #TODO: add skeletonDefinition return { "cameras": bool, "smoothingGroups": bool, @@ -65,7 +65,8 @@ class FBXExtractor: "inputConnections": bool, "upAxis": str, # x, y or z, "triangulate": bool, - "FileVersion": str + "FileVersion": str, + "skeletonDefinitions": bool } @property @@ -107,7 +108,8 @@ class FBXExtractor: "inputConnections": True, "upAxis": "y", "triangulate": False, - "fileVersion": "FBX202000" + "fileVersion": "FBX202000", + "skeletonDefinitions": False } def __init__(self, log=None): diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index d571975438..c9f3fea027 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -33,7 +33,8 @@ class CollectRigFbx(pyblish.api.InstancePlugin): skeleton_content = cmds.sets(skeleton_set, query=True) if skeleton_content: instance.data["animated_rigs"] += skeleton_content - self.log.debug(f"Collected skeleton data: {skeleton_content}") + self.log.debug("Collected skeleton" + f" data: {skeleton_content}") if skeleton_mesh_sets: instance.data["families"].append("rig.fbx") for skeleton_mesh_set in skeleton_mesh_sets: diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 2ac4734d21..b35cfbc271 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -42,6 +42,7 @@ class ExtractRigFBX(publish.Extractor, out_set = instance.data.get("animated_skeleton", []) instance.data["constraints"] = True + instance.data["skeletonDefinitions"] = True instance.data["animationOnly"] = True fbx_exporter.set_options_from_instance(instance) diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index 0df602fa29..122cfecf3c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -42,6 +42,7 @@ class ExtractRigFBX(publish.Extractor, out_set = instance.data.get("skeleton_mesh", []) instance.data["constraints"] = True + instance.data["skeletonDefinitions"] = True fbx_exporter.set_options_from_instance(instance) @@ -50,7 +51,6 @@ class ExtractRigFBX(publish.Extractor, if "representations" not in instance.data: instance.data["representations"] = [] - self.log.debug("Families: {}".format(instance.data["families"])) representation = { 'name': 'fbx', @@ -59,6 +59,5 @@ class ExtractRigFBX(publish.Extractor, "stagingDir": staging_dir, } instance.data["representations"].append(representation) - self.log.debug("Representation: {}".format(representation)) self.log.debug("Extract FBX successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py new file mode 100644 index 0000000000..df434f132d --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +"""Plugin for validating naming conventions.""" +from maya import cmds + +import pyblish.api + +from openpype.pipeline.publish import ( + ValidateContentsOrder, + OptionalPyblishPluginMixin, + PublishValidationError +) + + +class ValidateSkeletonTopGroupHierarchy(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): + """Validates top group hierarchy in the SETs + Make sure the object inside the SETs are always top + group of the hierarchy + + """ + order = ValidateContentsOrder + 0.05 + label = "Top Group Hierarchy" + families = ["rig"] + + def process(self, instance): + invalid = [] + skeleton_data = instance.data.get(("animated_rigs"), []) + skeletonMesh_data = instance.data(("skeleton_mesh"), []) + if skeleton_data: + invalid = self.get_top_hierarchy(skeleton_data) + if invalid: + raise PublishValidationError( + "The set includes the object which " + f"is not at the top hierarchy: {invalid}") + if skeletonMesh_data: + invalid = self.get_top_hierarchy(skeletonMesh_data) + if invalid: + raise PublishValidationError( + "The set includes the object which " + f"is not at the top hierarchy: {invalid}") + + def get_top_hierarchy(self, targets): + non_top_hierarchy_list = [] + for target in targets: + long_names = cmds.ls(target, long=True) + for name in long_names: + if len(name.split["|"]) > 2: + non_top_hierarchy_list.append(name) + return non_top_hierarchy_list 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 e8300282d7..e5fe367e77 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 @@ -824,6 +824,10 @@ { "key": "ValidateSkeletonRigOutputIds", "label": "Validate Skeleton Rig Output Ids" + }, + { + "key": "ValidateSkeletonTopGroupHierarchy", + "label": "Validate Skeleton Top Group Hierarchy" } ] }, diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 6e3179b78e..0c733d9cbc 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -676,6 +676,10 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Skeleton Rig Output Ids" ) + ValidateSkeletonTopGroupHierarchy: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Skeleton Top Group Hierarchy", + ) ValidateRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field( default_factory=ValidateRigOutSetNodeIdsModel, title="Validate Rig Out Set Node Ids", @@ -1209,6 +1213,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "active": True }, + "ValidateSkeletonTopGroupHierarchy": { + "enabled": False, + "optional": True, + "active": True + }, "ValidateCameraAttributes": { "enabled": False, "optional": True, From c07e741b7a2a35982da423c017485b3f3687bedc Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Sep 2023 13:35:55 +0800 Subject: [PATCH 16/59] hound --- openpype/hosts/maya/api/fbx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 306b7efe0b..9092aaec23 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -40,7 +40,7 @@ class FBXExtractor: the option is not included and a warning is logged. """ - #TODO: add skeletonDefinition + return { "cameras": bool, "smoothingGroups": bool, From 4bbb2e0ba35a902a618f97e1ee3d9cdde5de8135 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Sep 2023 13:40:43 +0800 Subject: [PATCH 17/59] add the validator into maya settings --- openpype/settings/defaults/project_settings/maya.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 2bc226c431..022b906c4f 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1170,6 +1170,11 @@ "optional": true, "active": true }, + "ValidateSkeletonTopGroupHierarchy": { + "enabled": false, + "optional": true, + "active": true + }, "ValidateCameraAttributes": { "enabled": false, "optional": true, From b72d241c2fc0e659fb879224a58883d1f5ba4db2 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 13 Sep 2023 14:00:04 +0800 Subject: [PATCH 18/59] bigRoy's comment --- openpype/hosts/maya/plugins/publish/extract_fbx_animation.py | 5 +---- openpype/hosts/maya/plugins/publish/extract_rig_fbx.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index b35cfbc271..cf6cb39628 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -21,14 +21,11 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Animation (FBX)" - families = ["animation"] + families = ["animation.fbx"] def process(self, instance): if not self.is_active(instance.data): return - if "animation.fbx" not in instance.data["families"]: - self.log.debug("No object inside skeleton_set..Skipping...") - return if not cmds.loadPlugin("fbxmaya", query=True): cmds.loadPlugin("fbxmaya", quiet=True) # Define output path diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index 122cfecf3c..a81e9deaa1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -21,14 +21,11 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Rig (FBX)" - families = ["rig"] + families = ["rig.fbx"] def process(self, instance): if not self.is_active(instance.data): return - if "rig.fbx" not in instance.data["families"]: - self.log.debug("No object inside skeletonMesh_set..Skipping..") - return if not cmds.loadPlugin("fbxmaya", query=True): cmds.loadPlugin("fbxmaya", quiet=True) From 6d411cdbc952271276843041528951d0f228a7de Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Sep 2023 00:36:18 +0800 Subject: [PATCH 19/59] bug fix on Libor's comment --- .../hosts/maya/plugins/publish/collect_rig_for_fbx.py | 1 + .../hosts/maya/plugins/publish/extract_fbx_animation.py | 7 ++++--- openpype/hosts/maya/plugins/publish/extract_rig_fbx.py | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index c9f3fea027..215a2dd6f3 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -28,6 +28,7 @@ class CollectRigFbx(pyblish.api.InstancePlugin): "no skeleton_set or skeleton_mesh set was found....") return instance.data["skeleton_mesh"] = [] + instance.data["animated_rigs"] = [] if skeleton_sets: for skeleton_set in skeleton_sets: skeleton_content = cmds.sets(skeleton_set, query=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index cf6cb39628..3c2b76c20d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -21,13 +21,14 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Animation (FBX)" - families = ["animation.fbx"] + families = ["animation"] def process(self, instance): if not self.is_active(instance.data): return - if not cmds.loadPlugin("fbxmaya", query=True): - cmds.loadPlugin("fbxmaya", quiet=True) + if "animation.fbx" not in instance.data["families"]: + self.log.debug("No object inside skeletonAnim_set..Skipping..") + return # Define output path staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index a81e9deaa1..570ef2c267 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -21,14 +21,14 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Rig (FBX)" - families = ["rig.fbx"] + families = ["rig"] def process(self, instance): if not self.is_active(instance.data): return - if not cmds.loadPlugin("fbxmaya", query=True): - cmds.loadPlugin("fbxmaya", quiet=True) - + if "rig.fbx" not in instance.data["families"]: + self.log.debug("No object inside skeletonMesh_set..Skipping..") + return staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) path = os.path.join(staging_dir, filename) From 5f67ffdeb035a249a5528bf82256c023b9a7ae90 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Sep 2023 00:39:04 +0800 Subject: [PATCH 20/59] ondrej's comment on the frame range --- openpype/hosts/maya/plugins/publish/collect_fbx_animation.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index 72501dc819..8a4e7360a8 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -11,10 +11,6 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin): families = ["animation"] def process(self, instance): - frame = cmds.currentTime(query=True) - instance.data["frameStart"] = frame - instance.data["frameEnd"] = frame - skeleton_sets = [ i for i in instance if i.lower().endswith("skeletonanim_set") From a2c72e683e2ac3420931a0721a9f3d12f843db96 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 14 Sep 2023 17:10:46 +0800 Subject: [PATCH 21/59] add maya as hosts --- .../hosts/maya/plugins/publish/collect_fbx_animation.py | 1 + openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py | 1 + .../hosts/maya/plugins/publish/extract_fbx_animation.py | 6 ++---- openpype/hosts/maya/plugins/publish/extract_rig_fbx.py | 7 ++----- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index 8a4e7360a8..9749fb4770 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -8,6 +8,7 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.2 label = "Collect Fbx Animation" + hosts = ["maya"] families = ["animation"] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py index 215a2dd6f3..65653b3369 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py @@ -8,6 +8,7 @@ class CollectRigFbx(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.2 label = "Collect rig for fbx" + hosts = ["maya"] families = ["rig"] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 3c2b76c20d..1b4b63db87 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -21,14 +21,12 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Animation (FBX)" - families = ["animation"] + hosts = ["maya"] + families = ["animation.fbx"] def process(self, instance): if not self.is_active(instance.data): return - if "animation.fbx" not in instance.data["families"]: - self.log.debug("No object inside skeletonAnim_set..Skipping..") - return # Define output path staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index 570ef2c267..9eecde90e9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -21,18 +21,15 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder label = "Extract Rig (FBX)" - families = ["rig"] + hosts = ["maya"] + families = ["rig.fbx"] def process(self, instance): if not self.is_active(instance.data): return - if "rig.fbx" not in instance.data["families"]: - self.log.debug("No object inside skeletonMesh_set..Skipping..") - return staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) path = os.path.join(staging_dir, filename) - # The export requires forward slashes because we need # to format it into a string in a mel expression fbx_exporter = fbx.FBXExtractor(log=self.log) From c78d496ec948e1108ee23f2babf17878ddaa26fa Mon Sep 17 00:00:00 2001 From: Kayla Date: Mon, 18 Sep 2023 12:17:22 +0800 Subject: [PATCH 22/59] including skeleton sets into animation sets during loading rig & fixing the rig fbx extractor not being displayed --- openpype/hosts/maya/api/lib.py | 16 ++++++++++++++-- .../plugins/publish/collect_fbx_animation.py | 5 +++-- ..._rig_for_fbx.py => collect_skeleton_mesh.py} | 17 +++++------------ .../plugins/publish/extract_fbx_animation.py | 2 +- .../maya/plugins/publish/extract_rig_fbx.py | 14 ++++++++------ 5 files changed, 31 insertions(+), 23 deletions(-) rename openpype/hosts/maya/plugins/publish/{collect_rig_for_fbx.py => collect_skeleton_mesh.py} (67%) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 40b3419e73..2ff4ff42de 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4109,6 +4109,14 @@ def create_rig_animation_instance( assert output, "No out_SET in rig, this is a bug." assert controls, "No controls_SET in rig, this is a bug." + anim_skeleton = next((node for node in nodes if + node.endswith("skeletonAnim_SET")), None) + if not anim_skeleton: + log.debug("No skeletonAnim_SET in rig") + skeleton_mesh = next((node for node in nodes if + node.endswith("skeletonMesh_SET")), None) + if not skeleton_mesh: + log.debug("No skeletonMesh_SET in rig") # Find the roots amongst the loaded nodes roots = ( cmds.ls(nodes, assemblies=True, long=True) or @@ -4142,10 +4150,14 @@ def create_rig_animation_instance( host = registered_host() create_context = CreateContext(host) - # Create the animation instance + rig_sets = [output, controls] + if anim_skeleton: + rig_sets.append(anim_skeleton) + if skeleton_mesh: + rig_sets.append(skeleton_mesh) with maintained_selection(): - cmds.select([output, controls] + roots, noExpand=True) + cmds.select(rig_sets + roots, noExpand=True) create_context.create( creator_identifier=creator_identifier, variant=namespace, diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index 9749fb4770..75e36e78ce 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -4,7 +4,7 @@ import pyblish.api class CollectFbxAnimation(pyblish.api.InstancePlugin): - """Collect Unreal Skeletal Mesh.""" + """Collect Animated Rig Data for FBX Extractor.""" order = pyblish.api.CollectorOrder + 0.2 label = "Collect Fbx Animation" @@ -17,7 +17,8 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin): if i.lower().endswith("skeletonanim_set") ] if skeleton_sets: - instance.data["families"].append("animation.fbx") + instance.data["families"] += ["animation.fbx"] + instance.data["animated_skeleton"] = [] for skeleton_set in skeleton_sets: skeleton_content = cmds.sets(skeleton_set, query=True) self.log.debug( diff --git a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py similarity index 67% rename from openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py rename to openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index 65653b3369..ccf65441a2 100644 --- a/openpype/hosts/maya/plugins/publish/collect_rig_for_fbx.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -3,11 +3,11 @@ from maya import cmds # noqa import pyblish.api -class CollectRigFbx(pyblish.api.InstancePlugin): - """Collect Unreal Skeletal Mesh.""" +class CollectSkeletonMesh(pyblish.api.InstancePlugin): + """Collect Static Rig Data for FBX Extractor.""" order = pyblish.api.CollectorOrder + 0.2 - label = "Collect rig for fbx" + label = "Collect Skeleton Mesh" hosts = ["maya"] families = ["rig"] @@ -29,16 +29,9 @@ class CollectRigFbx(pyblish.api.InstancePlugin): "no skeleton_set or skeleton_mesh set was found....") return instance.data["skeleton_mesh"] = [] - instance.data["animated_rigs"] = [] - if skeleton_sets: - for skeleton_set in skeleton_sets: - skeleton_content = cmds.sets(skeleton_set, query=True) - if skeleton_content: - instance.data["animated_rigs"] += skeleton_content - self.log.debug("Collected skeleton" - f" data: {skeleton_content}") + if skeleton_mesh_sets: - instance.data["families"].append("rig.fbx") + instance.data["families"] += ["rig.fbx"] for skeleton_mesh_set in skeleton_mesh_sets: skeleton_mesh_content = cmds.sets( skeleton_mesh_set, query=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 1b4b63db87..ef8b22d452 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -44,7 +44,7 @@ class ExtractRigFBX(publish.Extractor, fbx_exporter.set_options_from_instance(instance) # Export - fbx_exporter.export(out_set, path) + fbx_exporter.export(out_set, path.replace("\\", "/")) if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py index 9eecde90e9..c9fe53f0be 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py @@ -9,8 +9,8 @@ from openpype.pipeline.publish import OptionalPyblishPluginMixin from openpype.hosts.maya.api import fbx -class ExtractRigFBX(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractSkeletonMesh(publish.Extractor, + OptionalPyblishPluginMixin): """Extract Rig in FBX format from Maya. This extracts the rig in fbx with the constraints @@ -20,16 +20,18 @@ class ExtractRigFBX(publish.Extractor, """ order = pyblish.api.ExtractorOrder - label = "Extract Rig (FBX)" + label = "Extract Skeleton Mesh" hosts = ["maya"] families = ["rig.fbx"] def process(self, instance): if not self.is_active(instance.data): return + # Define output path staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) path = os.path.join(staging_dir, filename) + # The export requires forward slashes because we need # to format it into a string in a mel expression fbx_exporter = fbx.FBXExtractor(log=self.log) @@ -41,7 +43,7 @@ class ExtractRigFBX(publish.Extractor, fbx_exporter.set_options_from_instance(instance) # Export - fbx_exporter.export(out_set, path) + fbx_exporter.export(out_set, path.replace("\\", "/")) if "representations" not in instance.data: instance.data["representations"] = [] @@ -50,8 +52,8 @@ class ExtractRigFBX(publish.Extractor, 'name': 'fbx', 'ext': 'fbx', 'files': filename, - "stagingDir": staging_dir, + "stagingDir": staging_dir } instance.data["representations"].append(representation) - self.log.debug("Extract FBX successful to: {0}".format(path)) + self.log.debug("Extract animated FBX successful to: {0}".format(path)) From b5d789e09bdc576c38197d590abcb23999fcb635 Mon Sep 17 00:00:00 2001 From: Kayla Date: Mon, 18 Sep 2023 20:36:00 +0800 Subject: [PATCH 23/59] remove animationOnly parameter as it would convert the joint to transform data --- openpype/hosts/maya/api/fbx.py | 7 ++++--- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 7 +++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 9092aaec23..c06ba12719 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -66,7 +66,8 @@ class FBXExtractor: "upAxis": str, # x, y or z, "triangulate": bool, "FileVersion": str, - "skeletonDefinitions": bool + "skeletonDefinitions": bool, + "referencedAssetsContent": bool } @property @@ -97,7 +98,6 @@ class FBXExtractor: "bakeComplexEnd": end_frame, "bakeComplexStep": 1, "bakeResampleAnimation": True, - "animationOnly": False, "useSceneName": False, "quaternion": "euler", "shapes": True, @@ -109,7 +109,8 @@ class FBXExtractor: "upAxis": "y", "triangulate": False, "fileVersion": "FBX202000", - "skeletonDefinitions": False + "skeletonDefinitions": False, + "referencedAssetsContent": False } def __init__(self, log=None): diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index ef8b22d452..8e96d46344 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -2,6 +2,7 @@ import os from maya import cmds # noqa +import maya.mel as mel import pyblish.api from openpype.pipeline import publish @@ -36,14 +37,12 @@ class ExtractRigFBX(publish.Extractor, # to format it into a string in a mel expression fbx_exporter = fbx.FBXExtractor(log=self.log) out_set = instance.data.get("animated_skeleton", []) - + # Export instance.data["constraints"] = True instance.data["skeletonDefinitions"] = True - instance.data["animationOnly"] = True + instance.data["referencedAssetsContent"] = True fbx_exporter.set_options_from_instance(instance) - - # Export fbx_exporter.export(out_set, path.replace("\\", "/")) if "representations" not in instance.data: From 5429616e1ee894582afdc3422a7b53b079da9495 Mon Sep 17 00:00:00 2001 From: Kayla Date: Mon, 18 Sep 2023 21:50:13 +0800 Subject: [PATCH 24/59] add fbx as representation to the loader and hound fix --- openpype/hosts/maya/api/fbx.py | 1 - openpype/hosts/maya/api/lib.py | 8 ++++---- openpype/hosts/maya/plugins/load/_load_animation.py | 2 +- openpype/hosts/maya/plugins/load/load_reference.py | 2 +- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 4 +--- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index c06ba12719..5bd375362b 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -54,7 +54,6 @@ class FBXExtractor: "bakeComplexEnd": int, "bakeComplexStep": int, "bakeResampleAnimation": bool, - "animationOnly": bool, "useSceneName": bool, "quaternion": str, # "euler" "shapes": bool, diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 2ff4ff42de..2769f05c35 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4100,14 +4100,14 @@ def create_rig_animation_instance( """ if options is None: options = {} - + name = context["representation"]["name"] output = next((node for node in nodes if node.endswith("out_SET")), None) controls = next((node for node in nodes if node.endswith("controls_SET")), None) - - assert output, "No out_SET in rig, this is a bug." - assert controls, "No controls_SET in rig, this is a bug." + if name != "fbx": + assert output, "No out_SET in rig, this is a bug." + assert controls, "No controls_SET in rig, this is a bug." anim_skeleton = next((node for node in nodes if node.endswith("skeletonAnim_SET")), None) diff --git a/openpype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py index 981b9ef434..6d67383909 100644 --- a/openpype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -7,7 +7,7 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): families = ["animation", "camera", "pointcache"] - representations = ["abc"] + representations = ["abc", "fbx"] label = "Reference animation" order = -10 diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 61f337f501..c9c3fb9786 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -117,7 +117,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): family = context["representation"]["context"]["family"] except ValueError: family = "model" - + print(f"family:{family}") project_name = context["project"]["name"] # True by default to keep legacy behaviours attach_to_root = options.get("attach_to_root", True) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 8e96d46344..142d815a29 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -2,7 +2,6 @@ import os from maya import cmds # noqa -import maya.mel as mel import pyblish.api from openpype.pipeline import publish @@ -52,8 +51,7 @@ class ExtractRigFBX(publish.Extractor, 'name': 'fbx', 'ext': 'fbx', 'files': filename, - "stagingDir": staging_dir, - "outputName": "fbxanim" + "stagingDir": staging_dir } instance.data["representations"].append(representation) From 7252acceb89f7b175bf2b22235bb8ab66d0dbe03 Mon Sep 17 00:00:00 2001 From: Kayla Date: Mon, 18 Sep 2023 21:52:17 +0800 Subject: [PATCH 25/59] hound --- openpype/hosts/maya/api/lib.py | 2 +- openpype/hosts/maya/plugins/load/load_reference.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 2769f05c35..d889fe4b8c 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4100,7 +4100,7 @@ def create_rig_animation_instance( """ if options is None: options = {} - name = context["representation"]["name"] + name = context["representation"]["name"] output = next((node for node in nodes if node.endswith("out_SET")), None) controls = next((node for node in nodes if diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index c9c3fb9786..61f337f501 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -117,7 +117,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): family = context["representation"]["context"]["family"] except ValueError: family = "model" - print(f"family:{family}") + project_name = context["project"]["name"] # True by default to keep legacy behaviours attach_to_root = options.get("attach_to_root", True) From b7995a41a71fc8d0e9593f7c9940a8e7f06de9dc Mon Sep 17 00:00:00 2001 From: Kayla Date: Wed, 20 Sep 2023 21:26:02 +0800 Subject: [PATCH 26/59] enable the skeleton rig content validator and make the fbx animation collector optional and use the asset as both asset_name and asset_type data for custom subset in the loader --- openpype/hosts/maya/api/lib.py | 6 ++- .../plugins/publish/collect_fbx_animation.py | 9 ++++- .../plugins/publish/collect_skeleton_mesh.py | 10 +++++ .../plugins/publish/extract_fbx_animation.py | 6 +-- ...ct_rig_fbx.py => extract_skeleton_mesh.py} | 0 .../publish/validate_skeleton_rig_content.py | 40 +++++++++---------- .../defaults/project_settings/maya.json | 5 ++- .../schemas/schema_maya_publish.json | 14 +++++++ .../maya/server/settings/publishers.py | 13 +++++- 9 files changed, 72 insertions(+), 31 deletions(-) rename openpype/hosts/maya/plugins/publish/{extract_rig_fbx.py => extract_skeleton_mesh.py} (100%) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index d889fe4b8c..fed2887419 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4113,6 +4113,7 @@ def create_rig_animation_instance( node.endswith("skeletonAnim_SET")), None) if not anim_skeleton: log.debug("No skeletonAnim_SET in rig") + skeleton_mesh = next((node for node in nodes if node.endswith("skeletonMesh_SET")), None) if not skeleton_mesh: @@ -4128,8 +4129,9 @@ def create_rig_animation_instance( if custom_subset: formatting_data = { # TODO remove 'asset_type' and replace 'asset_name' with 'asset' - "asset_name": context['asset']['name'], - "asset_type": context['asset']['type'], + # "asset_name": context['asset']['name'], + # "asset_type": context['asset']['type'], + "asset": context["asset"], "subset": context['subset']['name'], "family": ( context['subset']['data'].get('family') or diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index 75e36e78ce..061619dfb1 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -1,17 +1,22 @@ # -*- coding: utf-8 -*- from maya import cmds # noqa import pyblish.api +from openpype.lib import BoolDef +from openpype.pipeline import OptionalPyblishPluginMixin - -class CollectFbxAnimation(pyblish.api.InstancePlugin): +class CollectFbxAnimation(pyblish.api.InstancePlugin, + OptionalPyblishPluginMixin): """Collect Animated Rig Data for FBX Extractor.""" order = pyblish.api.CollectorOrder + 0.2 label = "Collect Fbx Animation" hosts = ["maya"] families = ["animation"] + optional = True def process(self, instance): + if not self.is_active(instance.data): + return skeleton_sets = [ i for i in instance if i.lower().endswith("skeletonanim_set") diff --git a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index ccf65441a2..5d894c99a0 100644 --- a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -29,6 +29,7 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): "no skeleton_set or skeleton_mesh set was found....") return instance.data["skeleton_mesh"] = [] + instance.data["skeleton_rig"] = [] if skeleton_mesh_sets: instance.data["families"] += ["rig.fbx"] @@ -40,3 +41,12 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): self.log.debug( "Collected skeleton " f"mesh Set: {skeleton_mesh_content}") + + if skeleton_sets: + for skeleton_set in skeleton_sets: + skeleton_content = cmds.sets(skeleton_set, query=True) + self.log.debug( + "Collected animated " + f"skeleton data: {skeleton_content}") + if skeleton_content: + instance.data["skeleton_rig"] += skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 142d815a29..1c0a0135d2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -5,12 +5,10 @@ from maya import cmds # noqa import pyblish.api from openpype.pipeline import publish -from openpype.pipeline.publish import OptionalPyblishPluginMixin from openpype.hosts.maya.api import fbx -class ExtractRigFBX(publish.Extractor, - OptionalPyblishPluginMixin): +class ExtractFBXAnimation(publish.Extractor): """Extract Rig in FBX format from Maya. This extracts the rig in fbx with the constraints @@ -25,8 +23,6 @@ class ExtractRigFBX(publish.Extractor, families = ["animation.fbx"] def process(self, instance): - if not self.is_active(instance.data): - return # Define output path staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) diff --git a/openpype/hosts/maya/plugins/publish/extract_rig_fbx.py b/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py similarity index 100% rename from openpype/hosts/maya/plugins/publish/extract_rig_fbx.py rename to openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index 0406b00ec6..8b8800af17 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -12,7 +12,8 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): The rigs optionally contain at least two object sets: "skeletonAnim_SET" - Set of only bone hierarchies - "skeletonMesh_SET" - Set of all cacheable meshes + "skeletonMesh_SET" - Set of the skinned meshes + with bone hierarchies """ @@ -21,11 +22,10 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["rig.fbx"] - accepted_output = ["mesh", "transform"] - accepted_controllers = ["transform"] + accepted_output = ["mesh", "transform", "locator"] + accepted_controllers = ["transform", "locator"] def process(self, instance): - objectsets = ["skeletonAnim_SET", "skeletonMesh_SET"] missing = [ key for key in objectsets if key not in instance.data["rig_sets"] @@ -36,8 +36,8 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): ) return - controls_set = instance.data["rig_sets"]["skeletonAnim_SET"] - out_set = instance.data["rig_sets"]["skeletonMesh_SET"] + skeleton_anim_set = instance.data["rig_sets"]["skeletonAnim_SET"] + skeleton_mesh_set = instance.data["rig_sets"]["skeletonMesh_SET"] # Ensure there are at least some transforms or dag nodes # in the rig instance set_members = instance.data['setMembers'] @@ -45,13 +45,13 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): self.log.debug("Skipping empty instance...") return # Ensure contents in sets and retrieve long path for all objects - output_content = cmds.sets( - out_set, query=True) or [] - output_content = cmds.ls(output_content, long=True) + skeleton_mesh_content = cmds.sets( + skeleton_mesh_set, query=True) or [] + skeleton_mesh_content = cmds.ls(skeleton_mesh_content, long=True) - controls_content = cmds.sets( - controls_set, query=True) or [] - controls_content = cmds.ls(controls_content, long=True) + skeleton_anim_content = cmds.sets( + skeleton_anim_set, query=True) or [] + skeleton_anim_content = cmds.ls(skeleton_anim_content, long=True) # Validate members are inside the hierarchy from root node root_node = cmds.ls(set_members, assemblies=True) @@ -60,16 +60,16 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): hierarchy = set(hierarchy) invalid_hierarchy = [] - if output_content: - for node in output_content: + if skeleton_mesh_content: + for node in skeleton_mesh_content: if node not in hierarchy: invalid_hierarchy.append(node) - invalid_geometry = self.validate_geometry(output_content) - if controls_content: - for node in controls_content: + invalid_geometry = self.validate_geometry(skeleton_mesh_content) + if skeleton_anim_content: + for node in skeleton_anim_content: if node not in hierarchy: invalid_hierarchy.append(node) - invalid_controls = self.validate_controls(controls_content) + invalid_controls = self.validate_controls(skeleton_anim_content) error = False if invalid_hierarchy: @@ -99,7 +99,7 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): Checks if the node types of the set members valid Args: - set_members: list of nodes of the controls_SET + set_members: list of nodes of the skeleton_mesh_set hierarchy: list of nodes which reside under the root node Returns: @@ -126,7 +126,7 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): Checks if the node types of the set members valid Args: - set_members: list of nodes of the controls_SET + set_members: list of nodes of the skeleton_anim_set hierarchy: list of nodes which reside under the root node Returns: diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 022b906c4f..f4fb38ab53 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -707,6 +707,9 @@ "CollectMayaRender": { "sync_workfile_version": false }, + "CollectFbxAnimation": { + "enabled": true + }, "CollectFbxCamera": { "enabled": false }, @@ -1141,7 +1144,7 @@ "active": true }, "ValidateSkeletonRigContents": { - "enabled": false, + "enabled": true, "optional": true, "active": true }, 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 e5fe367e77..6d81f38aa9 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 @@ -21,6 +21,20 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectFbxAnimation", + "label": "Collect Fbx Animation", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { "type": "dict", "collapsible": true, diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index 0c733d9cbc..d82daa178c 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -129,6 +129,10 @@ class CollectMayaRenderModel(BaseSettingsModel): ) +class CollectFbxAnimationModel(BaseSettingsModel): + enabled: bool = Field(title="Collect Fbx Animation") + + class CollectFbxCameraModel(BaseSettingsModel): enabled: bool = Field(title="CollectFbxCamera") @@ -364,6 +368,10 @@ class PublishersModel(BaseSettingsModel): title="Collect Render Layers", section="Collectors" ) + CollectFbxAnimation: CollectFbxAnimationModel = Field( + default_factory=CollectFbxAnimationModel, + title="Collect FBX Animation", + ) CollectFbxCamera: CollectFbxCameraModel = Field( default_factory=CollectFbxCameraModel, title="Collect Camera for FBX export", @@ -768,6 +776,9 @@ DEFAULT_PUBLISH_SETTINGS = { "CollectMayaRender": { "sync_workfile_version": False }, + "CollectFbxAnimation": { + "enabled": True + }, "CollectFbxCamera": { "enabled": False }, @@ -1184,7 +1195,7 @@ DEFAULT_PUBLISH_SETTINGS = { "active": True }, "ValidateSkeletonRigContents": { - "enabled": False, + "enabled": True, "optional": True, "active": True }, From 7dd64ff210078716b80271ab33e81a4ba7266993 Mon Sep 17 00:00:00 2001 From: Kayla Date: Sun, 24 Sep 2023 13:07:03 +0800 Subject: [PATCH 27/59] temporarily remove namespace for fbx export and restore namespace after export --- .../plugins/publish/extract_fbx_animation.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 1c0a0135d2..1d683b2eb7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -30,6 +30,8 @@ class ExtractFBXAnimation(publish.Extractor): # The export requires forward slashes because we need # to format it into a string in a mel expression + + fbx_exporter = fbx.FBXExtractor(log=self.log) out_set = instance.data.get("animated_skeleton", []) # Export @@ -38,7 +40,21 @@ class ExtractFBXAnimation(publish.Extractor): instance.data["referencedAssetsContent"] = True fbx_exporter.set_options_from_instance(instance) - fbx_exporter.export(out_set, path.replace("\\", "/")) + + out_set_name = next(out for out in out_set) + # temporarily disable namespace + namespace = out_set_name.split(":")[0] + new_out_set = out_set_name.replace( + f"{namespace}:", "") + cmds.namespace(set=':') + cmds.namespace(set=namespace) + cmds.namespace(rel=True) + + fbx_exporter.export( + new_out_set, path.replace("\\", "/")) + # restore namespace after export + cmds.namespace(set=':') + cmds.namespace(rel=False) if "representations" not in instance.data: instance.data["representations"] = [] From ce104345236e42fa53ba428672a6c810b60007cb Mon Sep 17 00:00:00 2001 From: Kayla Date: Sun, 24 Sep 2023 13:09:04 +0800 Subject: [PATCH 28/59] hound --- openpype/hosts/maya/plugins/publish/extract_fbx_animation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 1d683b2eb7..fb7001bb99 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -30,8 +30,6 @@ class ExtractFBXAnimation(publish.Extractor): # The export requires forward slashes because we need # to format it into a string in a mel expression - - fbx_exporter = fbx.FBXExtractor(log=self.log) out_set = instance.data.get("animated_skeleton", []) # Export From 72737702b6fd92a066ea51191752c6d9d10d57f9 Mon Sep 17 00:00:00 2001 From: Kayla Date: Sun, 24 Sep 2023 13:10:08 +0800 Subject: [PATCH 29/59] hound --- openpype/hosts/maya/plugins/publish/collect_fbx_animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index 061619dfb1..d89236a73c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- from maya import cmds # noqa import pyblish.api -from openpype.lib import BoolDef from openpype.pipeline import OptionalPyblishPluginMixin + class CollectFbxAnimation(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin): """Collect Animated Rig Data for FBX Extractor.""" From 6a4ab981ad2a9f5b6f9d3225a260bb43e7d4ac9b Mon Sep 17 00:00:00 2001 From: Kayla Date: Mon, 25 Sep 2023 23:00:25 +0800 Subject: [PATCH 30/59] add validator to make sure all nodes are refernce nodes in skeleton_Anim_SET --- .../publish/validate_animated_reference.py | 31 +++++++++++++++++++ .../defaults/project_settings/maya.json | 5 +++ .../schemas/schema_maya_publish.json | 4 +++ .../maya/server/settings/publishers.py | 9 ++++++ 4 files changed, 49 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/validate_animated_reference.py diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py new file mode 100644 index 0000000000..8bf9c61d0d --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -0,0 +1,31 @@ +import pyblish.api +import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ( + PublishValidationError, + ValidateContentsOrder +) +from maya import cmds + + +class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): + """ + Validate all the nodes underneath skeleton_Anim_SET + should be reference nodes + """ + + order = ValidateContentsOrder + hosts = ["maya"] + families = ["animation.fbx"] + label = "Animated Reference Rig" + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + + def process(self, instance): + animated_sets = instance.data["animated_skeleton"] + for animated_reference in animated_sets: + is_referenced = cmds.referenceQuery( + animated_reference, isNodeReferenced=True) + if not bool(is_referenced): + raise PublishValidationError( + "All the content in skeleton_Anim_SET" + " should be reference nodes" + ) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index f4fb38ab53..d3e01287e5 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1123,6 +1123,11 @@ "optional": true, "active": true }, + "ValidateAnimatedReferenceRig": { + "enabled": true, + "optional": false, + "active": true + }, "ValidateAnimationContent": { "enabled": true, "optional": false, 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 6d81f38aa9..f2bbc0f70b 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 @@ -807,6 +807,10 @@ "key": "ValidateRigControllers", "label": "Validate Rig Controllers" }, + { + "key": "ValidateAnimatedReferenceRig", + "label": "Validate Animated Reference Rig" + }, { "key": "ValidateAnimationContent", "label": "Validate Animation Content" diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index d82daa178c..cb3af191a8 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -652,6 +652,10 @@ class PublishersModel(BaseSettingsModel): default_factory=BasicValidateModel, title="Validate Rig Controllers", ) + ValidateAnimatedReferenceRig: BasicValidateModel = Field( + default_factory=BasicValidateModel, + title="Validate Animated Reference Rig", + ) ValidateAnimationContent: BasicValidateModel = Field( default_factory=BasicValidateModel, title="Validate Animation Content", @@ -1174,6 +1178,11 @@ DEFAULT_PUBLISH_SETTINGS = { "optional": True, "active": True }, + "ValidateAnimatedReferenceRig": { + "enabled": True, + "optional": False, + "active": True + }, "ValidateAnimationContent": { "enabled": True, "optional": False, From 8fd323fb16a52a2a0dbd3979cc4eaf7d37dca40d Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 26 Sep 2023 12:49:24 +0800 Subject: [PATCH 31/59] add the validation to make sure the skeleton_Anim_SET should be bone hierarchy only --- .../publish/validate_animated_reference.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py index 8bf9c61d0d..0034599976 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -18,9 +18,15 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): families = ["animation.fbx"] label = "Animated Reference Rig" actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + accepted_controllers = ["transform", "locator"] def process(self, instance): animated_sets = instance.data["animated_skeleton"] + if not animated_sets: + self.log.debug( + "No nodes found in skeleton_Anim_SET..Skipping..") + return + for animated_reference in animated_sets: is_referenced = cmds.referenceQuery( animated_reference, isNodeReferenced=True) @@ -29,3 +35,30 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): "All the content in skeleton_Anim_SET" " should be reference nodes" ) + invalid_controls = self.validate_controls(animated_sets) + if invalid_controls: + raise PublishValidationError( + "All the content in skeleton_Anim_SET" + " should be the transforms" + ) + def validate_controls(self, set_members): + """Check if the controller set passes the validations + + Checks if all its set members are within the hierarchy of the root + Checks if the node types of the set members valid + + Args: + set_members: list of nodes of the skeleton_anim_set + hierarchy: list of nodes which reside under the root node + + Returns: + errors (list) + """ + + # Validate control types + invalid = [] + for node in set_members: + if cmds.nodeType(node) not in self.accepted_controllers: + invalid.append(node) + + return invalid From b33ddb05de211e71151b95f139aae8f99c30e874 Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 26 Sep 2023 12:51:14 +0800 Subject: [PATCH 32/59] hound --- .../maya/plugins/publish/validate_animated_reference.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py index 0034599976..63c0b6958d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -38,9 +38,10 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): invalid_controls = self.validate_controls(animated_sets) if invalid_controls: raise PublishValidationError( - "All the content in skeleton_Anim_SET" - " should be the transforms" - ) + "All the content in skeleton_Anim_SET" + " should be the transforms" + ) + def validate_controls(self, set_members): """Check if the controller set passes the validations From ed02bf31114e885a157c8ef13f8474cc86d093a1 Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 17:51:08 +0800 Subject: [PATCH 33/59] remove invalid actions and some code tweaks --- openpype/hosts/maya/plugins/publish/collect_fbx_animation.py | 2 +- .../hosts/maya/plugins/publish/validate_animated_reference.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index d89236a73c..ff7d068d7d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -22,7 +22,7 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin, if i.lower().endswith("skeletonanim_set") ] if skeleton_sets: - instance.data["families"] += ["animation.fbx"] + instance.data["families"].append("animation.fbx") instance.data["animated_skeleton"] = [] for skeleton_set in skeleton_sets: skeleton_content = cmds.sets(skeleton_set, query=True) diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py index 63c0b6958d..c1b5a2852d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -1,5 +1,4 @@ import pyblish.api -import openpype.hosts.maya.api.action from openpype.pipeline.publish import ( PublishValidationError, ValidateContentsOrder @@ -17,7 +16,6 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["animation.fbx"] label = "Animated Reference Rig" - actions = [openpype.hosts.maya.api.action.SelectInvalidAction] accepted_controllers = ["transform", "locator"] def process(self, instance): From a433c46e727862a28a0a4835f582135b588e67e6 Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 19:50:44 +0800 Subject: [PATCH 34/59] code tweak on extract fbx animation --- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index fb7001bb99..f8b7c18614 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -54,15 +54,12 @@ class ExtractFBXAnimation(publish.Extractor): cmds.namespace(set=':') cmds.namespace(rel=False) - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { + representations = instance.data.setdefault("representations", []) + representations.append({ 'name': 'fbx', 'ext': 'fbx', 'files': filename, "stagingDir": staging_dir - } - instance.data["representations"].append(representation) + }) self.log.debug("Extract animated FBX successful to: {0}".format(path)) From dda932e83ef448c74533ea39f687a6d919a2551c Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 22:37:30 +0800 Subject: [PATCH 35/59] code clean up and tweak on debug mesg --- openpype/hosts/maya/api/lib.py | 11 ---------- .../hosts/maya/plugins/create/create_rig.py | 4 ++-- .../plugins/publish/collect_fbx_animation.py | 22 ++++++++++--------- .../plugins/publish/collect_skeleton_mesh.py | 22 +++++++++---------- .../plugins/publish/extract_fbx_animation.py | 17 +++++++------- .../plugins/publish/extract_skeleton_mesh.py | 12 +++++----- .../publish/validate_animated_reference.py | 15 +++++-------- .../publish/validate_skeleton_rig_content.py | 6 ++--- .../validate_skeleton_top_group_hierarchy.py | 10 ++++----- 9 files changed, 50 insertions(+), 69 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index fed2887419..03a864a1db 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4111,13 +4111,7 @@ def create_rig_animation_instance( anim_skeleton = next((node for node in nodes if node.endswith("skeletonAnim_SET")), None) - if not anim_skeleton: - log.debug("No skeletonAnim_SET in rig") - skeleton_mesh = next((node for node in nodes if - node.endswith("skeletonMesh_SET")), None) - if not skeleton_mesh: - log.debug("No skeletonMesh_SET in rig") # Find the roots amongst the loaded nodes roots = ( cmds.ls(nodes, assemblies=True, long=True) or @@ -4128,9 +4122,6 @@ def create_rig_animation_instance( custom_subset = options.get("animationSubsetName") if custom_subset: formatting_data = { - # TODO remove 'asset_type' and replace 'asset_name' with 'asset' - # "asset_name": context['asset']['name'], - # "asset_type": context['asset']['type'], "asset": context["asset"], "subset": context['subset']['name'], "family": ( @@ -4156,8 +4147,6 @@ def create_rig_animation_instance( rig_sets = [output, controls] if anim_skeleton: rig_sets.append(anim_skeleton) - if skeleton_mesh: - rig_sets.append(skeleton_mesh) with maintained_selection(): cmds.select(rig_sets + roots, noExpand=True) create_context.create( diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 69c7787905..22a94ed4fd 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -20,9 +20,9 @@ class CreateRig(plugin.MayaCreator): instance_node = instance.get("instance_node") self.log.info("Creating Rig instance set up ...") - # change name (_controls_set -> _rigs_SET) + # TODO:change name (_controls_set -> _rigs_SET) controls = cmds.sets(name=subset_name + "_controls_SET", empty=True) - # change name (_out_SET -> _geo_SET) + # TODO:change name (_out_SET -> _geo_SET) pointcache = cmds.sets(name=subset_name + "_out_SET", empty=True) skeleton = cmds.sets( name=subset_name + "_skeletonAnim_SET", empty=True) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index ff7d068d7d..9347936e63 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -21,13 +21,15 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin, i for i in instance if i.lower().endswith("skeletonanim_set") ] - if skeleton_sets: - instance.data["families"].append("animation.fbx") - instance.data["animated_skeleton"] = [] - for skeleton_set in skeleton_sets: - skeleton_content = cmds.sets(skeleton_set, query=True) - self.log.debug( - "Collected animated " - f"skeleton data: {skeleton_content}") - if skeleton_content: - instance.data["animated_skeleton"] += skeleton_content + if not skeleton_sets: + return + + instance.data["families"].append("animation.fbx") + instance.data["animated_skeleton"] = [] + for skeleton_set in skeleton_sets: + skeleton_content = cmds.sets(skeleton_set, query=True) + self.log.debug( + "Collected animated " + f"skeleton data: {skeleton_content}") + if skeleton_content: + instance.data["animated_skeleton"] += skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index 5d894c99a0..73b2103618 100644 --- a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -12,22 +12,20 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): families = ["rig"] def process(self, instance): + skeleton_sets = instance.data.get("skeletonAnim_SET") + skeleton_mesh_sets = instance.data.get("skeletonMesh_SET") + if not skeleton_mesh_sets: + self.log.debug( + "skeletonMesh_SET found. " + "Skipping collecting of skeleton mesh..." + ) + return + + # Store current frame to ensure single frame export frame = cmds.currentTime(query=True) instance.data["frameStart"] = frame instance.data["frameEnd"] = frame - skeleton_sets = [ - i for i in instance - if i.lower().endswith("skeletonanim_set") - ] - skeleton_mesh_sets = [ - i for i in instance - if i.lower().endswith("skeletonmesh_set") - ] - if not skeleton_sets and skeleton_mesh_sets: - self.log.debug( - "no skeleton_set or skeleton_mesh set was found....") - return instance.data["skeleton_mesh"] = [] instance.data["skeleton_rig"] = [] diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index f8b7c18614..748f30e43d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -6,6 +6,7 @@ import pyblish.api from openpype.pipeline import publish from openpype.hosts.maya.api import fbx +from openpype.hosts.maya.api.lib import namespaced class ExtractFBXAnimation(publish.Extractor): @@ -27,9 +28,8 @@ class ExtractFBXAnimation(publish.Extractor): staging_dir = self.staging_dir(instance) filename = "{0}.fbx".format(instance.name) path = os.path.join(staging_dir, filename) + path = path.replace("\\", "/") - # The export requires forward slashes because we need - # to format it into a string in a mel expression fbx_exporter = fbx.FBXExtractor(log=self.log) out_set = instance.data.get("animated_skeleton", []) # Export @@ -44,12 +44,11 @@ class ExtractFBXAnimation(publish.Extractor): namespace = out_set_name.split(":")[0] new_out_set = out_set_name.replace( f"{namespace}:", "") - cmds.namespace(set=':') - cmds.namespace(set=namespace) - cmds.namespace(rel=True) - - fbx_exporter.export( - new_out_set, path.replace("\\", "/")) + cmds.namespace(set=':' + namespace) + cmds.namespace(relativeNames=True) + with namespaced(":" + namespace, new=False) as namespace: + fbx_exporter.export( + new_out_set, path.replace("\\", "/")) # restore namespace after export cmds.namespace(set=':') cmds.namespace(rel=False) @@ -62,4 +61,4 @@ class ExtractFBXAnimation(publish.Extractor): "stagingDir": staging_dir }) - self.log.debug("Extract animated FBX successful to: {0}".format(path)) + self.log.debug("Extracted Fbx animation successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py index c9fe53f0be..42cbb33013 100644 --- a/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py @@ -43,17 +43,15 @@ class ExtractSkeletonMesh(publish.Extractor, fbx_exporter.set_options_from_instance(instance) # Export - fbx_exporter.export(out_set, path.replace("\\", "/")) + path = path.replace("\\", "/") + fbx_exporter.export(out_set, path) - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { + representations = instance.data.setdefault("representations", []) + representations.append({ 'name': 'fbx', 'ext': 'fbx', 'files': filename, "stagingDir": staging_dir - } - instance.data["representations"].append(representation) + }) self.log.debug("Extract animated FBX successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py index c1b5a2852d..3dc272d7cc 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -7,10 +7,7 @@ from maya import cmds class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): - """ - Validate all the nodes underneath skeleton_Anim_SET - should be reference nodes - """ + """Validate all nodes in skeletonAnim_SET are referenced""" order = ValidateContentsOrder hosts = ["maya"] @@ -22,7 +19,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): animated_sets = instance.data["animated_skeleton"] if not animated_sets: self.log.debug( - "No nodes found in skeleton_Anim_SET..Skipping..") + "No nodes found in skeletonAnim_SET.Skipping...") return for animated_reference in animated_sets: @@ -30,14 +27,14 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): animated_reference, isNodeReferenced=True) if not bool(is_referenced): raise PublishValidationError( - "All the content in skeleton_Anim_SET" - " should be reference nodes" + "All the content in skeletonAnim_SET" + " should be referenced nodes" ) invalid_controls = self.validate_controls(animated_sets) if invalid_controls: raise PublishValidationError( - "All the content in skeleton_Anim_SET" - " should be the transforms" + "All the content in skeletonAnim_SET" + " should be transforms" ) def validate_controls(self, set_members): diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index 8b8800af17..c7e724b569 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -45,12 +45,10 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): self.log.debug("Skipping empty instance...") return # Ensure contents in sets and retrieve long path for all objects - skeleton_mesh_content = cmds.sets( - skeleton_mesh_set, query=True) or [] + skeleton_mesh_content = instance.data.get("skeleton_mesh", []) skeleton_mesh_content = cmds.ls(skeleton_mesh_content, long=True) - skeleton_anim_content = cmds.sets( - skeleton_anim_set, query=True) or [] + skeleton_anim_content = instance.data.get("skeleton_rig", []) skeleton_anim_content = cmds.ls(skeleton_anim_content, long=True) # Validate members are inside the hierarchy from root node diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py index df434f132d..1e0d856b4e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py @@ -25,18 +25,18 @@ class ValidateSkeletonTopGroupHierarchy(pyblish.api.InstancePlugin, def process(self, instance): invalid = [] skeleton_data = instance.data.get(("animated_rigs"), []) - skeletonMesh_data = instance.data(("skeleton_mesh"), []) + skeleton_mesh_data = instance.data(("skeleton_mesh"), []) if skeleton_data: invalid = self.get_top_hierarchy(skeleton_data) if invalid: raise PublishValidationError( - "The set includes the object which " + "The skeletonAnim_SET includes the object which " f"is not at the top hierarchy: {invalid}") - if skeletonMesh_data: - invalid = self.get_top_hierarchy(skeletonMesh_data) + if skeleton_mesh_data: + invalid = self.get_top_hierarchy(skeleton_mesh_data) if invalid: raise PublishValidationError( - "The set includes the object which " + "The skeletonMesh_SET includes the object which " f"is not at the top hierarchy: {invalid}") def get_top_hierarchy(self, targets): From 0829adceda7fb8258b47dc7eb9a94691e789c77b Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 22:41:07 +0800 Subject: [PATCH 36/59] hound --- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 6 ++---- .../maya/plugins/publish/validate_skeleton_rig_content.py | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 748f30e43d..e99e7d40bd 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -49,9 +49,6 @@ class ExtractFBXAnimation(publish.Extractor): with namespaced(":" + namespace, new=False) as namespace: fbx_exporter.export( new_out_set, path.replace("\\", "/")) - # restore namespace after export - cmds.namespace(set=':') - cmds.namespace(rel=False) representations = instance.data.setdefault("representations", []) representations.append({ @@ -61,4 +58,5 @@ class ExtractFBXAnimation(publish.Extractor): "stagingDir": staging_dir }) - self.log.debug("Extracted Fbx animation successful to: {0}".format(path)) + self.log.debug( + "Extracted Fbx animation successful to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index c7e724b569..59595d5a1c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -36,8 +36,6 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): ) return - skeleton_anim_set = instance.data["rig_sets"]["skeletonAnim_SET"] - skeleton_mesh_set = instance.data["rig_sets"]["skeletonMesh_SET"] # Ensure there are at least some transforms or dag nodes # in the rig instance set_members = instance.data['setMembers'] From 276c6a81cd93509fbebc1808955275d9729d936f Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 22:42:51 +0800 Subject: [PATCH 37/59] message tweak --- .../hosts/maya/plugins/publish/validate_skeleton_rig_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index 59595d5a1c..a620c2f631 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -40,7 +40,7 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): # in the rig instance set_members = instance.data['setMembers'] if not cmds.ls(set_members, type="dagNode", long=True): - self.log.debug("Skipping empty instance...") + self.log.debug("Skipping instance without dag nodes...") return # Ensure contents in sets and retrieve long path for all objects skeleton_mesh_content = instance.data.get("skeleton_mesh", []) From 63e294147652c999b1bd0d4325c601830afb9bac Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 22:46:19 +0800 Subject: [PATCH 38/59] remove skeleton_anim_set in collector and validation check on rig content --- .../plugins/publish/collect_skeleton_mesh.py | 11 ------ .../publish/validate_skeleton_rig_content.py | 36 +------------------ 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index 73b2103618..a22901357b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -12,7 +12,6 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): families = ["rig"] def process(self, instance): - skeleton_sets = instance.data.get("skeletonAnim_SET") skeleton_mesh_sets = instance.data.get("skeletonMesh_SET") if not skeleton_mesh_sets: self.log.debug( @@ -27,7 +26,6 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): instance.data["frameEnd"] = frame instance.data["skeleton_mesh"] = [] - instance.data["skeleton_rig"] = [] if skeleton_mesh_sets: instance.data["families"] += ["rig.fbx"] @@ -39,12 +37,3 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): self.log.debug( "Collected skeleton " f"mesh Set: {skeleton_mesh_content}") - - if skeleton_sets: - for skeleton_set in skeleton_sets: - skeleton_content = cmds.sets(skeleton_set, query=True) - self.log.debug( - "Collected animated " - f"skeleton data: {skeleton_content}") - if skeleton_content: - instance.data["skeleton_rig"] += skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index a620c2f631..565295494a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -26,7 +26,7 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): accepted_controllers = ["transform", "locator"] def process(self, instance): - objectsets = ["skeletonAnim_SET", "skeletonMesh_SET"] + objectsets = ["skeletonMesh_SET"] missing = [ key for key in objectsets if key not in instance.data["rig_sets"] ] @@ -46,8 +46,6 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): skeleton_mesh_content = instance.data.get("skeleton_mesh", []) skeleton_mesh_content = cmds.ls(skeleton_mesh_content, long=True) - skeleton_anim_content = instance.data.get("skeleton_rig", []) - skeleton_anim_content = cmds.ls(skeleton_anim_content, long=True) # Validate members are inside the hierarchy from root node root_node = cmds.ls(set_members, assemblies=True) @@ -61,11 +59,6 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): if node not in hierarchy: invalid_hierarchy.append(node) invalid_geometry = self.validate_geometry(skeleton_mesh_content) - if skeleton_anim_content: - for node in skeleton_anim_content: - if node not in hierarchy: - invalid_hierarchy.append(node) - invalid_controls = self.validate_controls(skeleton_anim_content) error = False if invalid_hierarchy: @@ -74,11 +67,6 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): "\n%s" % invalid_hierarchy) error = True - if invalid_controls: - self.log.error("Only transforms can be part of the " - "skeletonAnim_SET. \n%s" % invalid_controls) - error = True - if invalid_geometry: self.log.error("Only meshes can be part of the " "skeletonMesh_SET\n%s" % invalid_geometry) @@ -114,25 +102,3 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): invalid.append(shape) return invalid - - def validate_controls(self, set_members): - """Check if the controller set passes the validations - - Checks if all its set members are within the hierarchy of the root - Checks if the node types of the set members valid - - Args: - set_members: list of nodes of the skeleton_anim_set - hierarchy: list of nodes which reside under the root node - - Returns: - errors (list) - """ - - # Validate control types - invalid = [] - for node in set_members: - if cmds.nodeType(node) not in self.accepted_controllers: - invalid.append(node) - - return invalid From c5d54a522aad24886adda213c495ea657e9c1567 Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 22:51:53 +0800 Subject: [PATCH 39/59] make sure skeleton_Anim set and skeleton_Mesh set inside the loaded rig_sets --- openpype/hosts/maya/api/lib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 03a864a1db..b246a77512 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4111,6 +4111,8 @@ def create_rig_animation_instance( anim_skeleton = next((node for node in nodes if node.endswith("skeletonAnim_SET")), None) + skeleton_mesh = next((node for node in nodes if + node.endswith("skeletonMesh_SET")), None) # Find the roots amongst the loaded nodes roots = ( @@ -4147,6 +4149,8 @@ def create_rig_animation_instance( rig_sets = [output, controls] if anim_skeleton: rig_sets.append(anim_skeleton) + if skeleton_mesh: + rig_sets.append(skeleton_mesh) with maintained_selection(): cmds.select(rig_sets + roots, noExpand=True) create_context.create( From 1e7c544e902112fa9c0621fd629f438bf3cd0a36 Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 22:57:30 +0800 Subject: [PATCH 40/59] hound --- .../hosts/maya/plugins/publish/validate_skeleton_rig_content.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index 565295494a..9be7861309 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -46,7 +46,6 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): skeleton_mesh_content = instance.data.get("skeleton_mesh", []) skeleton_mesh_content = cmds.ls(skeleton_mesh_content, long=True) - # Validate members are inside the hierarchy from root node root_node = cmds.ls(set_members, assemblies=True) hierarchy = cmds.listRelatives(root_node, allDescendents=True, From 5a4ef31f4e06cec2ac49ec9486cf08dc95ceb7ad Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 23:03:10 +0800 Subject: [PATCH 41/59] remove animated_rig instance data in rig family --- .../maya/plugins/publish/validate_skeleton_rig_content.py | 2 -- .../publish/validate_skeleton_top_group_hierarchy.py | 7 ------- 2 files changed, 9 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index 9be7861309..09c5bb5bdc 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -11,7 +11,6 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): """Ensure skeleton rigs contains pipeline-critical content The rigs optionally contain at least two object sets: - "skeletonAnim_SET" - Set of only bone hierarchies "skeletonMesh_SET" - Set of the skinned meshes with bone hierarchies @@ -23,7 +22,6 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): families = ["rig.fbx"] accepted_output = ["mesh", "transform", "locator"] - accepted_controllers = ["transform", "locator"] def process(self, instance): objectsets = ["skeletonMesh_SET"] diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py index 1e0d856b4e..541efee9a9 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py @@ -24,14 +24,7 @@ class ValidateSkeletonTopGroupHierarchy(pyblish.api.InstancePlugin, def process(self, instance): invalid = [] - skeleton_data = instance.data.get(("animated_rigs"), []) skeleton_mesh_data = instance.data(("skeleton_mesh"), []) - if skeleton_data: - invalid = self.get_top_hierarchy(skeleton_data) - if invalid: - raise PublishValidationError( - "The skeletonAnim_SET includes the object which " - f"is not at the top hierarchy: {invalid}") if skeleton_mesh_data: invalid = self.get_top_hierarchy(skeleton_mesh_data) if invalid: From a7b99ac0b0e347c74be0d41a15837bc7b0f2b17d Mon Sep 17 00:00:00 2001 From: Kayla Date: Thu, 28 Sep 2023 23:19:06 +0800 Subject: [PATCH 42/59] make sure the namespace has been restored --- openpype/hosts/maya/plugins/publish/extract_fbx_animation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index e99e7d40bd..1647bbdcda 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -50,6 +50,8 @@ class ExtractFBXAnimation(publish.Extractor): fbx_exporter.export( new_out_set, path.replace("\\", "/")) + cmds.namespace(relativeNames=False) + representations = instance.data.setdefault("representations", []) representations.append({ 'name': 'fbx', From ea4ce1b8be7a721124445770a6169ab960145b3a Mon Sep 17 00:00:00 2001 From: Kayla Date: Fri, 29 Sep 2023 15:17:41 +0800 Subject: [PATCH 43/59] make sure the namespace has not been forcily restored --- .../maya/plugins/publish/extract_fbx_animation.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 1647bbdcda..d281e01779 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -44,13 +44,14 @@ class ExtractFBXAnimation(publish.Extractor): namespace = out_set_name.split(":")[0] new_out_set = out_set_name.replace( f"{namespace}:", "") - cmds.namespace(set=':' + namespace) cmds.namespace(relativeNames=True) with namespaced(":" + namespace, new=False) as namespace: - fbx_exporter.export( - new_out_set, path.replace("\\", "/")) - - cmds.namespace(relativeNames=False) + path = path.replace("\\", "/") + fbx_exporter.export(new_out_set, path) + original_relative_names = cmds.namespace( + query=True, relativeNames=True) + if original_relative_names: + cmds.namespace(relativeNames=original_relative_names) representations = instance.data.setdefault("representations", []) representations.append({ From 37cefd892c85218dc3614341866b5332c7384e10 Mon Sep 17 00:00:00 2001 From: Kayla Date: Fri, 29 Sep 2023 18:26:58 +0800 Subject: [PATCH 44/59] abstract namespaced functions for extract fbx animation and add fbx loaders in animatin family --- openpype/hosts/maya/api/fbx.py | 3 + openpype/hosts/maya/api/lib.py | 5 +- .../hosts/maya/plugins/create/create_rig.py | 2 +- .../maya/plugins/load/_load_animation.py | 102 ++++++++++++------ .../plugins/publish/collect_fbx_animation.py | 5 +- .../plugins/publish/collect_skeleton_mesh.py | 5 +- .../plugins/publish/extract_fbx_animation.py | 21 ++-- .../plugins/publish/extract_skeleton_mesh.py | 3 - .../validate_skeleton_rig_output_ids.py | 6 +- .../validate_skeleton_top_group_hierarchy.py | 14 ++- 10 files changed, 102 insertions(+), 64 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 5bd375362b..2dd4f5a73d 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -203,6 +203,9 @@ class FBXExtractor: path (str): Path to use for export. """ + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace("\\", "/") with maintained_selection(): cmds.select(members, r=True, noExpand=True) mel.eval('FBXExport -f "{}" -s'.format(path)) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b246a77512..6019aec37c 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -922,7 +922,7 @@ def no_display_layers(nodes): @contextlib.contextmanager -def namespaced(namespace, new=True): +def namespaced(namespace, new=True, relative_names=None): """Work inside namespace during context Args: @@ -934,6 +934,7 @@ def namespaced(namespace, new=True): """ original = cmds.namespaceInfo(cur=True, absoluteName=True) + original_relative_names = cmds.namespace(query=True, relativeNames=True) if new: namespace = unique_namespace(namespace) cmds.namespace(add=namespace) @@ -943,6 +944,8 @@ def namespaced(namespace, new=True): yield namespace finally: cmds.namespace(set=original) + if relative_names is not None: + cmds.namespace(relativeNames=original_relative_names) @contextlib.contextmanager diff --git a/openpype/hosts/maya/plugins/create/create_rig.py b/openpype/hosts/maya/plugins/create/create_rig.py index 22a94ed4fd..acd5c98f89 100644 --- a/openpype/hosts/maya/plugins/create/create_rig.py +++ b/openpype/hosts/maya/plugins/create/create_rig.py @@ -20,7 +20,7 @@ class CreateRig(plugin.MayaCreator): instance_node = instance.get("instance_node") self.log.info("Creating Rig instance set up ...") - # TODO:change name (_controls_set -> _rigs_SET) + # TODO:change name (_controls_SET -> _rigs_SET) controls = cmds.sets(name=subset_name + "_controls_SET", empty=True) # TODO:change name (_out_SET -> _geo_SET) pointcache = cmds.sets(name=subset_name + "_out_SET", empty=True) diff --git a/openpype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py index 6d67383909..2432184151 100644 --- a/openpype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -1,4 +1,46 @@ import openpype.hosts.maya.api.plugin +import maya.cmds as cmds + + +def _process_reference(file_url, name, namespace, options): + """_summary_ + + Args: + file_url (str): fileapth of the objects to be loaded + name (str): subset name + namespace (str): namespace + options (dict): dict of storing the param + + Returns: + list: list of object nodes + """ + from openpype.hosts.maya.api.lib import unique_namespace + # Get name from asset being loaded + # Assuming name is subset name from the animation, we split the number + # suffix from the name to ensure the namespace is unique + name = name.split("_")[0] + ext = file_url.split(".")[-1] + namespace = unique_namespace( + "{}_".format(name), + format="%03d", + suffix="_{}".format(ext) + ) + + attach_to_root = options.get("attach_to_root", True) + group_name = options["group_name"] + + # no group shall be created + if not attach_to_root: + group_name = namespace + + nodes = cmds.file(file_url, + namespace=namespace, + sharedReferenceFile=False, + groupReference=attach_to_root, + groupName=group_name, + reference=True, + returnNewNodes=True) + return nodes class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -7,7 +49,7 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): families = ["animation", "camera", "pointcache"] - representations = ["abc", "fbx"] + representations = ["abc"] label = "Reference animation" order = -10 @@ -16,44 +58,42 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def process_reference(self, context, name, namespace, options): - import maya.cmds as cmds - from openpype.hosts.maya.api.lib import unique_namespace - cmds.loadPlugin("AbcImport.mll", quiet=True) - # Prevent identical alembic nodes from being shared - # Create unique namespace for the cameras - - # Get name from asset being loaded - # Assuming name is subset name from the animation, we split the number - # suffix from the name to ensure the namespace is unique - name = name.split("_")[0] - namespace = unique_namespace( - "{}_".format(name), - format="%03d", - suffix="_abc" - ) - - attach_to_root = options.get("attach_to_root", True) - group_name = options["group_name"] - - # no group shall be created - if not attach_to_root: - group_name = namespace - # hero_001 (abc) # asset_counter{optional} path = self.filepath_from_context(context) file_url = self.prepare_root_value(path, context["project"]["name"]) - nodes = cmds.file(file_url, - namespace=namespace, - sharedReferenceFile=False, - groupReference=attach_to_root, - groupName=group_name, - reference=True, - returnNewNodes=True) + nodes = _process_reference(file_url, name, namespace, options) # load colorbleed ID attribute self[:] = nodes return nodes + + +class FbxLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): + """Loader to reference an Fbx files""" + + families = ["animation", + "camera"] + representations = ["fbx"] + + label = "Reference animation" + order = -10 + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name, namespace, options): + + cmds.loadPlugin("fbx4maya.mll", quiet=True) + + path = self.filepath_from_context(context) + file_url = self.prepare_root_value(path, + context["project"]["name"]) + + nodes = _process_reference(file_url, name, namespace, options) + + self[:] = nodes + + return nodes diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index 9347936e63..ee5ac741c8 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -29,7 +29,8 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin, for skeleton_set in skeleton_sets: skeleton_content = cmds.sets(skeleton_set, query=True) self.log.debug( - "Collected animated " - f"skeleton data: {skeleton_content}") + "Collected animated skeleton data: {}".format( + skeleton_content + )) if skeleton_content: instance.data["animated_skeleton"] += skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index a22901357b..9169e3dc28 100644 --- a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -35,5 +35,6 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): if skeleton_mesh_content: instance.data["skeleton_mesh"] += skeleton_mesh_content self.log.debug( - "Collected skeleton " - f"mesh Set: {skeleton_mesh_content}") + "Collected skeletonmesh Set: {}".format( + skeleton_mesh_content + )) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index d281e01779..fbc1d5176c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -40,18 +40,15 @@ class ExtractFBXAnimation(publish.Extractor): fbx_exporter.set_options_from_instance(instance) out_set_name = next(out for out in out_set) - # temporarily disable namespace - namespace = out_set_name.split(":")[0] - new_out_set = out_set_name.replace( - f"{namespace}:", "") + # Export from the rig's namespace so that the exported + # FBX does not include the namespace but preserves the node + # names as existing in the rig workfile + namespace, relative_out_set = out_set_name.split(":", 1) cmds.namespace(relativeNames=True) - with namespaced(":" + namespace, new=False) as namespace: - path = path.replace("\\", "/") - fbx_exporter.export(new_out_set, path) - original_relative_names = cmds.namespace( - query=True, relativeNames=True) - if original_relative_names: - cmds.namespace(relativeNames=original_relative_names) + with namespaced( + ":" + namespace, + new=False, relative_names=True) as namespace: + fbx_exporter.export(relative_out_set, path) representations = instance.data.setdefault("representations", []) representations.append({ @@ -62,4 +59,4 @@ class ExtractFBXAnimation(publish.Extractor): }) self.log.debug( - "Extracted Fbx animation successful to: {0}".format(path)) + "Extracted Fbx animation to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py index 42cbb33013..cecdf282e2 100644 --- a/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py @@ -32,8 +32,6 @@ class ExtractSkeletonMesh(publish.Extractor, filename = "{0}.fbx".format(instance.name) path = os.path.join(staging_dir, filename) - # The export requires forward slashes because we need - # to format it into a string in a mel expression fbx_exporter = fbx.FBXExtractor(log=self.log) out_set = instance.data.get("skeleton_mesh", []) @@ -43,7 +41,6 @@ class ExtractSkeletonMesh(publish.Extractor, fbx_exporter.set_options_from_instance(instance) # Export - path = path.replace("\\", "/") fbx_exporter.export(out_set, path) representations = instance.data.setdefault("representations", []) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py index 0d1e702749..735ca27b39 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py @@ -68,9 +68,7 @@ class ValidateSkeletonRigOutputIds(pyblish.api.InstancePlugin): if shapes: instance_nodes.extend(shapes) - scene_nodes = cmds.ls(type="transform", long=True) - scene_nodes += cmds.ls(type="mesh", long=True) - scene_nodes = set(scene_nodes) - set(instance_nodes) + scene_nodes = cmds.ls(type=("transform", "mesh"), long=True) scene_nodes_by_basename = defaultdict(list) for node in scene_nodes: @@ -109,7 +107,7 @@ class ValidateSkeletonRigOutputIds(pyblish.api.InstancePlugin): for instance_node, matches in invalid_matches.items(): ids = set(get_id(node) for node in matches) - # If there are multiple scene ids matched, and error needs to be + # If there are multiple scene ids matched, an error needs to be # raised for manual correction. if len(ids) > 1: multiple_ids_match.append({"node": instance_node, diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py index 541efee9a9..553618aa50 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py @@ -24,19 +24,17 @@ class ValidateSkeletonTopGroupHierarchy(pyblish.api.InstancePlugin, def process(self, instance): invalid = [] - skeleton_mesh_data = instance.data(("skeleton_mesh"), []) + skeleton_mesh_data = instance.data("skeleton_mesh", []) if skeleton_mesh_data: invalid = self.get_top_hierarchy(skeleton_mesh_data) if invalid: raise PublishValidationError( "The skeletonMesh_SET includes the object which " - f"is not at the top hierarchy: {invalid}") + "is not at the top hierarchy: {}".format(invalid)) def get_top_hierarchy(self, targets): - non_top_hierarchy_list = [] - for target in targets: - long_names = cmds.ls(target, long=True) - for name in long_names: - if len(name.split["|"]) > 2: - non_top_hierarchy_list.append(name) + targets = cmds.ls(targets, long=True) # ensure long names + non_top_hierarchy_list = [ + target for target in targets if target.count("|") > 2 + ] return non_top_hierarchy_list From 08f47c77fd25cee3000bf4e86e21318586f87c43 Mon Sep 17 00:00:00 2001 From: Kayla Date: Fri, 29 Sep 2023 18:29:58 +0800 Subject: [PATCH 45/59] hound --- openpype/hosts/maya/plugins/publish/extract_fbx_animation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index fbc1d5176c..115ba39986 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -45,9 +45,7 @@ class ExtractFBXAnimation(publish.Extractor): # names as existing in the rig workfile namespace, relative_out_set = out_set_name.split(":", 1) cmds.namespace(relativeNames=True) - with namespaced( - ":" + namespace, - new=False, relative_names=True) as namespace: + with namespaced(":" + namespace,new=False, relative_names=True) as namespace: # noqa fbx_exporter.export(relative_out_set, path) representations = instance.data.setdefault("representations", []) From 846bd0fd590688ceb4640b00f8f0f3af2ce6fa65 Mon Sep 17 00:00:00 2001 From: Kayla Date: Fri, 29 Sep 2023 22:12:02 +0800 Subject: [PATCH 46/59] make sure there is a check in relative names in namespace before yield function & add docstring --- openpype/hosts/maya/api/lib.py | 3 ++- openpype/hosts/maya/plugins/load/_load_animation.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 6019aec37c..dc881879ac 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -938,7 +938,8 @@ def namespaced(namespace, new=True, relative_names=None): if new: namespace = unique_namespace(namespace) cmds.namespace(add=namespace) - + if relative_names is not None: + cmds.namespace(relativeNames=relative_names) try: cmds.namespace(set=namespace) yield namespace diff --git a/openpype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py index 2432184151..0781735bc4 100644 --- a/openpype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -3,7 +3,7 @@ import maya.cmds as cmds def _process_reference(file_url, name, namespace, options): - """_summary_ + """Load files by referencing scene in Maya. Args: file_url (str): fileapth of the objects to be loaded From f287144616a5341c6651cb5af5b49416af3b4e30 Mon Sep 17 00:00:00 2001 From: Kayla Date: Fri, 29 Sep 2023 23:28:39 +0800 Subject: [PATCH 47/59] make sure validators for skeleton mesh are in rig.fbx family --- .../maya/plugins/publish/validate_skeleton_rig_content.py | 2 +- .../plugins/publish/validate_skeleton_top_group_hierarchy.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py index 09c5bb5bdc..8b6cc74332 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py @@ -21,7 +21,7 @@ class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["rig.fbx"] - accepted_output = ["mesh", "transform", "locator"] + accepted_output = {"mesh", "transform", "locator"} def process(self, instance): objectsets = ["skeletonMesh_SET"] diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py index 553618aa50..1dbe1c454c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py +++ b/openpype/hosts/maya/plugins/publish/validate_skeleton_top_group_hierarchy.py @@ -19,8 +19,8 @@ class ValidateSkeletonTopGroupHierarchy(pyblish.api.InstancePlugin, """ order = ValidateContentsOrder + 0.05 - label = "Top Group Hierarchy" - families = ["rig"] + label = "Skeleton Rig Top Group Hierarchy" + families = ["rig.fbx"] def process(self, instance): invalid = [] From 00131ffd152cc27236d98a24a065a82a8bf1d566 Mon Sep 17 00:00:00 2001 From: Kayla Date: Sun, 1 Oct 2023 20:15:22 +0800 Subject: [PATCH 48/59] refactor the validators for skeletonMesh and use rig validators as abstract class & minor tweak on collectors and settings --- .../plugins/publish/collect_skeleton_mesh.py | 5 +- .../plugins/publish/validate_rig_contents.py | 117 +++++++-- .../publish/validate_rig_controllers.py | 50 +++- .../publish/validate_rig_out_set_node_ids.py | 33 ++- .../publish/validate_rig_output_ids.py | 25 +- .../publish/validate_skeleton_rig_content.py | 101 -------- .../validate_skeleton_rig_controller.py | 222 ------------------ .../validate_skeleton_rig_out_set_node_ids.py | 90 ------- .../validate_skeleton_rig_output_ids.py | 124 ---------- .../defaults/project_settings/maya.json | 2 +- .../schemas/schema_maya_publish.json | 2 +- .../maya/server/settings/publishers.py | 2 +- 12 files changed, 211 insertions(+), 562 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py delete mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py delete mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py delete mode 100644 openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py diff --git a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index 9169e3dc28..648029c3fc 100644 --- a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -12,7 +12,10 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): families = ["rig"] def process(self, instance): - skeleton_mesh_sets = instance.data.get("skeletonMesh_SET") + skeleton_mesh_sets = [ + i for i in instance + if i.lower().endswith("skeletonmesh_set") + ] if not skeleton_mesh_sets: self.log.debug( "skeletonMesh_SET found. " diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index 23f031a5db..5b8faf6cae 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -25,19 +25,26 @@ class ValidateRigContents(pyblish.api.InstancePlugin): accepted_controllers = ["transform"] def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise PublishValidationError( + "Invalid rig content. See log for details.") + + @classmethod + def get_invalid(cls, instance): # Find required sets by suffix - required = ["controls_SET", "out_SET"] + required, rig_sets = cls.get_nodes(instance) missing = [ - key for key in required if key not in instance.data["rig_sets"] + key for key in required if key not in rig_sets ] if missing: raise PublishValidationError( "%s is missing sets: %s" % (instance, ", ".join(missing)) ) - controls_set = instance.data["rig_sets"]["controls_SET"] - out_set = instance.data["rig_sets"]["out_SET"] + controls_set = rig_sets["controls_SET"] + out_set = rig_sets["out_SET"] # Ensure there are at least some transforms or dag nodes # in the rig instance @@ -76,31 +83,29 @@ class ValidateRigContents(pyblish.api.InstancePlugin): invalid_hierarchy.append(node) # Additional validations - invalid_geometry = self.validate_geometry(output_content) - invalid_controls = self.validate_controls(controls_content) + invalid_geometry = cls.validate_geometry(output_content) + invalid_controls = cls.validate_controls(controls_content) error = False if invalid_hierarchy: - self.log.error("Found nodes which reside outside of root group " + cls.log.error("Found nodes which reside outside of root group " "while they are set up for publishing." "\n%s" % invalid_hierarchy) error = True if invalid_controls: - self.log.error("Only transforms can be part of the controls_SET." + cls.log.error("Only transforms can be part of the controls_SET." "\n%s" % invalid_controls) error = True if invalid_geometry: - self.log.error("Only meshes can be part of the out_SET\n%s" + cls.log.error("Only meshes can be part of the out_SET\n%s" % invalid_geometry) error = True + return error - if error: - raise PublishValidationError( - "Invalid rig content. See log for details.") - - def validate_geometry(self, set_members): + @classmethod + def validate_geometry(cls, set_members): """Check if the out set passes the validations Checks if all its set members are within the hierarchy of the root @@ -122,12 +127,13 @@ class ValidateRigContents(pyblish.api.InstancePlugin): fullPath=True) or [] all_shapes = cmds.ls(set_members + shapes, long=True, shapes=True) for shape in all_shapes: - if cmds.nodeType(shape) not in self.accepted_output: + if cmds.nodeType(shape) not in cls.accepted_output: invalid.append(shape) return invalid - def validate_controls(self, set_members): + @classmethod + def validate_controls(cls, set_members): """Check if the controller set passes the validations Checks if all its set members are within the hierarchy of the root @@ -144,7 +150,84 @@ class ValidateRigContents(pyblish.api.InstancePlugin): # Validate control types invalid = [] for node in set_members: - if cmds.nodeType(node) not in self.accepted_controllers: + if cmds.nodeType(node) not in cls.accepted_controllers: invalid.append(node) return invalid + + @classmethod + def get_nodes(cls, instance): + objectsets = ["controls_SET", "out_SET"] + rig_sets_nodes = instance.data.get("rig_sets", []) + return objectsets, rig_sets_nodes + + +class ValidateSkeletonRigContents(ValidateRigContents): + """Ensure skeleton rigs contains pipeline-critical content + + The rigs optionally contain at least two object sets: + "skeletonMesh_SET" - Set of the skinned meshes + with bone hierarchies + + """ + + order = ValidateContentsOrder + label = "Skeleton Rig Contents" + hosts = ["maya"] + families = ["rig.fbx"] + + accepted_output = {"mesh", "transform", "locator"} + + @classmethod + def get_invalid(cls, instance): + objectsets, skeleton_mesh_nodes = cls.get_nodes(instance) + missing = [ + key for key in objectsets if key not in instance.data["rig_sets"] + ] + if missing: + cls.log.debug( + "%s is missing sets: %s" % (instance, ", ".join(missing)) + ) + return + + # Ensure there are at least some transforms or dag nodes + # in the rig instance + set_members = instance.data['setMembers'] + if not cmds.ls(set_members, type="dagNode", long=True): + raise PublishValidationError( + "No dag nodes in the pointcache instance. " + "(Empty instance?)" + ) + # Ensure contents in sets and retrieve long path for all objects + output_content = instance.data.get("skeleton_mesh", []) + output_content = cmds.ls(skeleton_mesh_nodes, long=True) + + # Validate members are inside the hierarchy from root node + root_nodes = cmds.ls(set_members, assemblies=True, long=True) + hierarchy = cmds.listRelatives(root_nodes, allDescendents=True, + fullPath=True) + root_nodes + hierarchy = set(hierarchy) + error = False + invalid_hierarchy = [] + if output_content: + for node in output_content: + if node not in hierarchy: + invalid_hierarchy.append(node) + invalid_geometry = cls.validate_geometry(output_content) + if invalid_hierarchy: + cls.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % invalid_hierarchy) + error = True + if invalid_geometry: + cls.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % invalid_hierarchy) + error = True + return error + + @classmethod + def get_nodes(cls, instance): + objectsets = ["skeletonMesh_SET"] + skeleton_mesh_nodes = instance.data.get("skeleton_mesh", []) + return objectsets, skeleton_mesh_nodes diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index a3828f871b..c1e3d96bae 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -59,7 +59,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - controls_set = instance.data["rig_sets"].get("controls_SET") + controls_set = cls.get_node(instance) if not controls_set: cls.log.error( "Must have 'controls_SET' in rig instance" @@ -189,7 +189,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - controls_set = instance.data["rig_sets"].get("controls_SET") + controls_set = cls.get_node(instance) if not controls_set: cls.log.error( "Unable to repair because no 'controls_SET' found in rig " @@ -228,3 +228,49 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): default = cls.CONTROLLER_DEFAULTS[attr] cls.log.info("Setting %s to %s" % (plug, default)) cmds.setAttr(plug, default) + @classmethod + def get_node(cls, instance): + return instance.data["rig_sets"].get("controls_SET") + + +class ValidateSkeletonRigControllers(ValidateRigControllers): + """Validate rig controller for skeletonAnim_SET + + Controls must have the transformation attributes on their default + values of translate zero, rotate zero and scale one when they are + unlocked attributes. + + Unlocked keyable attributes may not have any incoming connections. If + these connections are required for the rig then lock the attributes. + + The visibility attribute must be locked. + + Note that `repair` will: + - Lock all visibility attributes + - Reset all default values for translate, rotate, scale + - Break all incoming connections to keyable attributes + + """ + order = ValidateContentsOrder + 0.05 + label = "Skeleton Rig Controllers" + hosts = ["maya"] + families = ["rig.fbx"] + actions = [RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] + + # Default controller values + CONTROLLER_DEFAULTS = { + "translateX": 0, + "translateY": 0, + "translateZ": 0, + "rotateX": 0, + "rotateY": 0, + "rotateZ": 0, + "scaleX": 1, + "scaleY": 1, + "scaleZ": 1 + } + + @classmethod + def get_node(cls, instance): + return instance.data["rig_sets"].get("skeletonAnim_SET") diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index fbd510c683..00eca608a1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -46,7 +46,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): def get_invalid(cls, instance): """Get all nodes which do not match the criteria""" - out_set = instance.data["rig_sets"].get("out_SET") + out_set = cls.get_node(instance) if not out_set: return [] @@ -85,3 +85,34 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): continue lib.set_id(node, sibling_id, overwrite=True) + + @classmethod + def get_node(cls, instance): + return instance.data["rig_sets"].get("out_SET") + + +class ValidateSkeletonRigOutSetNodeIds(ValidateRigOutSetNodeIds): + """Validate if deformed shapes have related IDs to the original shapes + from skeleton set. + + When a deformer is applied in the scene on a referenced mesh that already + had deformers then Maya will create a new shape node for the mesh that + does not have the original id. This validator checks whether the ids are + valid on all the shape nodes in the instance. + + """ + + order = ValidateContentsOrder + families = ["rig.fbx"] + hosts = ['maya'] + label = 'Skeleton Rig Out Set Node Ids' + actions = [ + openpype.hosts.maya.api.action.SelectInvalidAction, + RepairAction + ] + allow_history_only = False + + @classmethod + def get_node(cls, instance): + return instance.data["rig_sets"].get( + "skeletonMesh_SET") diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index 24fb36eb8b..e6204902f0 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -47,7 +47,7 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): invalid = {} if compute: - out_set = instance.data["rig_sets"].get("out_SET") + out_set = cls.get_node(instance) if not out_set: instance.data["mismatched_output_ids"] = invalid return invalid @@ -115,3 +115,26 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): "Multiple matched ids found. Please repair manually: " "{}".format(multiple_ids_match) ) + + @classmethod + def get_node(cls, instance): + return instance.data["rig_sets"].get("out_SET") + + +class ValidateSkeletonRigOutputIds(ValidateRigOutputIds): + """Validate rig output ids from the skeleton sets. + + Ids must share the same id as similarly named nodes in the scene. This is + to ensure the id from the model is preserved through animation. + + """ + order = ValidateContentsOrder + 0.05 + label = "Skeleton Rig Output Ids" + hosts = ["maya"] + families = ["rig.fbx"] + actions = [RepairAction, + openpype.hosts.maya.api.action.SelectInvalidAction] + + @classmethod + def get_node(cls, instance): + return instance.data["rig_sets"].get("skeletonMesh_SET") diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py deleted file mode 100644 index 8b6cc74332..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_content.py +++ /dev/null @@ -1,101 +0,0 @@ -import pyblish.api -from maya import cmds - -from openpype.pipeline.publish import ( - PublishValidationError, - ValidateContentsOrder -) - - -class ValidateSkeletonRigContents(pyblish.api.InstancePlugin): - """Ensure skeleton rigs contains pipeline-critical content - - The rigs optionally contain at least two object sets: - "skeletonMesh_SET" - Set of the skinned meshes - with bone hierarchies - - """ - - order = ValidateContentsOrder - label = "Skeleton Rig Contents" - hosts = ["maya"] - families = ["rig.fbx"] - - accepted_output = {"mesh", "transform", "locator"} - - def process(self, instance): - objectsets = ["skeletonMesh_SET"] - missing = [ - key for key in objectsets if key not in instance.data["rig_sets"] - ] - if missing: - self.log.debug( - "%s is missing sets: %s" % (instance, ", ".join(missing)) - ) - return - - # Ensure there are at least some transforms or dag nodes - # in the rig instance - set_members = instance.data['setMembers'] - if not cmds.ls(set_members, type="dagNode", long=True): - self.log.debug("Skipping instance without dag nodes...") - return - # Ensure contents in sets and retrieve long path for all objects - skeleton_mesh_content = instance.data.get("skeleton_mesh", []) - skeleton_mesh_content = cmds.ls(skeleton_mesh_content, long=True) - - # Validate members are inside the hierarchy from root node - root_node = cmds.ls(set_members, assemblies=True) - hierarchy = cmds.listRelatives(root_node, allDescendents=True, - fullPath=True) - hierarchy = set(hierarchy) - - invalid_hierarchy = [] - if skeleton_mesh_content: - for node in skeleton_mesh_content: - if node not in hierarchy: - invalid_hierarchy.append(node) - invalid_geometry = self.validate_geometry(skeleton_mesh_content) - - error = False - if invalid_hierarchy: - self.log.error("Found nodes which reside outside of root group " - "while they are set up for publishing." - "\n%s" % invalid_hierarchy) - error = True - - if invalid_geometry: - self.log.error("Only meshes can be part of the " - "skeletonMesh_SET\n%s" % invalid_geometry) - error = True - - if error: - raise PublishValidationError( - "Invalid rig content. See log for details.") - - def validate_geometry(self, set_members): - """Check if the out set passes the validations - - Checks if all its set members are within the hierarchy of the root - Checks if the node types of the set members valid - - Args: - set_members: list of nodes of the skeleton_mesh_set - hierarchy: list of nodes which reside under the root node - - Returns: - errors (list) - """ - - # Validate all shape types - invalid = [] - shapes = cmds.listRelatives(set_members, - allDescendents=True, - shapes=True, - fullPath=True) or [] - all_shapes = cmds.ls(set_members + shapes, long=True, shapes=True) - for shape in all_shapes: - if cmds.nodeType(shape) not in self.accepted_output: - invalid.append(shape) - - return invalid diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py deleted file mode 100644 index a31d13bcec..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_controller.py +++ /dev/null @@ -1,222 +0,0 @@ -from maya import cmds - -import pyblish.api - -from openpype.pipeline.publish import ( - ValidateContentsOrder, - RepairAction, - PublishValidationError -) -import openpype.hosts.maya.api.action -from openpype.hosts.maya.api.lib import undo_chunk - - -class ValidateSkeletonRigControllers(pyblish.api.InstancePlugin): - """Validate rig controller for skeletonAnim_SET - - Controls must have the transformation attributes on their default - values of translate zero, rotate zero and scale one when they are - unlocked attributes. - - Unlocked keyable attributes may not have any incoming connections. If - these connections are required for the rig then lock the attributes. - - The visibility attribute must be locked. - - Note that `repair` will: - - Lock all visibility attributes - - Reset all default values for translate, rotate, scale - - Break all incoming connections to keyable attributes - - """ - order = ValidateContentsOrder + 0.05 - label = "Skeleton Rig Controllers" - hosts = ["maya"] - families = ["rig.fbx"] - actions = [RepairAction, - openpype.hosts.maya.api.action.SelectInvalidAction] - - # Default controller values - CONTROLLER_DEFAULTS = { - "translateX": 0, - "translateY": 0, - "translateZ": 0, - "rotateX": 0, - "rotateY": 0, - "rotateZ": 0, - "scaleX": 1, - "scaleY": 1, - "scaleZ": 1 - } - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError( - '{} failed, see log information'.format(self.label) - ) - - @classmethod - def get_invalid(cls, instance): - skeleton_set = instance.data["rig_sets"].get("skeletonAnim_SET") - if not skeleton_set: - cls.log.info( - "No 'skeletonAnim_SET' in rig instance" - ) - return - controls = cmds.sets(skeleton_set, query=True) - lookup = set(instance[:]) - if not all(control in lookup for control in cmds.ls(controls, - long=True)): - cls.log.error( - "All controls must be inside the rig's group." - ) - return [controls] - # Validate all controls - has_connections = list() - has_unlocked_visibility = list() - has_non_default_values = list() - for control in controls: - if cls.get_connected_attributes(control): - has_connections.append(control) - - # check if visibility is locked - attribute = "{}.visibility".format(control) - locked = cmds.getAttr(attribute, lock=True) - if not locked: - has_unlocked_visibility.append(control) - - if cls.get_non_default_attributes(control): - has_non_default_values.append(control) - - if has_connections: - cls.log.error("Controls have input connections: " - "%s" % has_connections) - - if has_non_default_values: - cls.log.error("Controls have non-default values: " - "%s" % has_non_default_values) - - if has_unlocked_visibility: - cls.log.error("Controls have unlocked visibility " - "attribute: %s" % has_unlocked_visibility) - - invalid = [] - if (has_connections or - has_unlocked_visibility or - has_non_default_values): - invalid = set() - invalid.update(has_connections) - invalid.update(has_non_default_values) - invalid.update(has_unlocked_visibility) - invalid = list(invalid) - cls.log.error("Invalid rig controllers. See log for details.") - - return invalid - - @classmethod - def get_non_default_attributes(cls, control): - """Return attribute plugs with non-default values - - Args: - control (str): Name of control node. - - Returns: - list: The invalid plugs - - """ - - invalid = [] - for attr, default in cls.CONTROLLER_DEFAULTS.items(): - if cmds.attributeQuery(attr, node=control, exists=True): - plug = "{}.{}".format(control, attr) - - # Ignore locked attributes - locked = cmds.getAttr(plug, lock=True) - if locked: - continue - - value = cmds.getAttr(plug) - if value != default: - cls.log.warning("Control non-default value: " - "%s = %s" % (plug, value)) - invalid.append(plug) - - return invalid - - @staticmethod - def get_connected_attributes(control): - """Return attribute plugs with incoming connections. - - This will also ensure no (driven) keys on unlocked keyable attributes. - - Args: - control (str): Name of control node. - - Returns: - list: The invalid plugs - - """ - import maya.cmds as mc - - # Support controls without any attributes returning None - attributes = mc.listAttr(control, keyable=True, scalar=True) or [] - invalid = [] - for attr in attributes: - plug = "{}.{}".format(control, attr) - - # Ignore locked attributes - locked = cmds.getAttr(plug, lock=True) - if locked: - continue - - # Ignore proxy connections. - if (cmds.addAttr(plug, query=True, exists=True) and - cmds.addAttr(plug, query=True, usedAsProxy=True)): - continue - - # Check for incoming connections - if cmds.listConnections(plug, source=True, destination=False): - invalid.append(plug) - - return invalid - - @classmethod - def repair(cls, instance): - skeleton_set = instance.data["rig_sets"].get("skeletonAnim_SET") - if not skeleton_set: - cls.log.error( - "Unable to repair because no 'skeletonAnim_SET' found in rig " - "instance: {}".format(instance) - ) - return - # Use a single undo chunk - with undo_chunk(): - controls = cmds.sets(skeleton_set, query=True) - for control in controls: - # Lock visibility - attr = "{}.visibility".format(control) - locked = cmds.getAttr(attr, lock=True) - if not locked: - cls.log.info("Locking visibility for %s" % control) - cmds.setAttr(attr, lock=True) - - # Remove incoming connections - invalid_plugs = cls.get_connected_attributes(control) - if invalid_plugs: - for plug in invalid_plugs: - cls.log.info("Breaking input connection to %s" % plug) - source = cmds.listConnections(plug, - source=True, - destination=False, - plugs=True)[0] - cmds.disconnectAttr(source, plug) - - # Reset non-default values - invalid_plugs = cls.get_non_default_attributes(control) - if invalid_plugs: - for plug in invalid_plugs: - attr = plug.split(".")[-1] - default = cls.CONTROLLER_DEFAULTS[attr] - cls.log.info("Setting %s to %s" % (plug, default)) - cmds.setAttr(plug, default) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py deleted file mode 100644 index 73ad12f422..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_out_set_node_ids.py +++ /dev/null @@ -1,90 +0,0 @@ -import maya.cmds as cmds - -import pyblish.api - -import openpype.hosts.maya.api.action -from openpype.hosts.maya.api import lib -from openpype.pipeline.publish import ( - RepairAction, - ValidateContentsOrder, - PublishValidationError -) - - -class ValidateSkeletonRigOutSetNodeIds(pyblish.api.InstancePlugin): - """Validate if deformed shapes have related IDs to the original shapes - from skeleton set. - - When a deformer is applied in the scene on a referenced mesh that already - had deformers then Maya will create a new shape node for the mesh that - does not have the original id. This validator checks whether the ids are - valid on all the shape nodes in the instance. - - """ - - order = ValidateContentsOrder - families = ["rig.fbx"] - hosts = ['maya'] - label = 'Skeleton Rig Out Set Node Ids' - actions = [ - openpype.hosts.maya.api.action.SelectInvalidAction, - RepairAction - ] - allow_history_only = False - - def process(self, instance): - """Process all meshes""" - - # Ensure all nodes have a cbId and a related ID to the original shapes - # if a deformer has been created on the shape - invalid = self.get_invalid(instance) - if invalid: - raise PublishValidationError( - "Nodes found with mismatching IDs: {0}".format(invalid) - ) - - @classmethod - def get_invalid(cls, instance): - """Get all nodes which do not match the criteria""" - - skeletonMesh_set = instance.data["rig_sets"].get( - "skeletonMesh_SET") - if not skeletonMesh_set: - return [] - - invalid = [] - members = cmds.sets(skeletonMesh_set, query=True) - shapes = cmds.ls(members, - dag=True, - leaf=True, - shapes=True, - long=True, - noIntermediate=True) - if not shapes: - return [] - for shape in shapes: - sibling_id = lib.get_id_from_sibling( - shape, - history_only=cls.allow_history_only - ) - if sibling_id: - current_id = lib.get_id(shape) - if current_id != sibling_id: - invalid.append(shape) - - return invalid - - @classmethod - def repair(cls, instance): - - for node in cls.get_invalid(instance): - # Get the original id from sibling - sibling_id = lib.get_id_from_sibling( - node, - history_only=cls.allow_history_only - ) - if not sibling_id: - cls.log.error("Could not find ID in siblings for '%s'", node) - continue - - lib.set_id(node, sibling_id, overwrite=True) diff --git a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py deleted file mode 100644 index 735ca27b39..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_skeleton_rig_output_ids.py +++ /dev/null @@ -1,124 +0,0 @@ -from collections import defaultdict - -from maya import cmds - -import pyblish.api - -import openpype.hosts.maya.api.action -from openpype.hosts.maya.api.lib import get_id, set_id -from openpype.pipeline.publish import ( - RepairAction, - ValidateContentsOrder, - PublishValidationError -) - - -def get_basename(node): - """Return node short name without namespace""" - return node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] - - -class ValidateSkeletonRigOutputIds(pyblish.api.InstancePlugin): - """Validate rig output ids from the skeleton sets. - - Ids must share the same id as similarly named nodes in the scene. This is - to ensure the id from the model is preserved through animation. - - """ - order = ValidateContentsOrder + 0.05 - label = "Skeleton Rig Output Ids" - hosts = ["maya"] - families = ["rig.fbx"] - actions = [RepairAction, - openpype.hosts.maya.api.action.SelectInvalidAction] - - def process(self, instance): - invalid = self.get_invalid(instance, compute=True) - if invalid: - raise PublishValidationError("Found nodes with mismatched IDs.") - - @classmethod - def get_invalid(cls, instance, compute=False): - invalid_matches = cls.get_invalid_matches(instance, compute=compute) - - invalid_skeleton_matches = cls.get_invalid_matches( - instance, compute=compute, set_name="skeletonMesh_SET") - invalid_matches.update(invalid_skeleton_matches) - return list(invalid_matches.keys()) - - @classmethod - def get_invalid_matches(cls, instance, compute=False): - invalid = {} - - if compute: - skeletonMesh_set = instance.data["rig_sets"].get( - "skeletonMesh_SET") - if not skeletonMesh_set: - instance.data["mismatched_output_ids"] = invalid - return invalid - - instance_nodes = cmds.sets( - skeletonMesh_set, query=True, nodesOnly=True) - - instance_nodes = cmds.ls(instance_nodes, long=True) - if not instance_nodes: - return {} - for node in instance_nodes: - shapes = cmds.listRelatives(node, shapes=True, fullPath=True) - if shapes: - instance_nodes.extend(shapes) - - scene_nodes = cmds.ls(type=("transform", "mesh"), long=True) - - scene_nodes_by_basename = defaultdict(list) - for node in scene_nodes: - basename = get_basename(node) - scene_nodes_by_basename[basename].append(node) - - for instance_node in instance_nodes: - basename = get_basename(instance_node) - if basename not in scene_nodes_by_basename: - continue - - matches = scene_nodes_by_basename[basename] - - ids = set(get_id(node) for node in matches) - ids.add(get_id(instance_node)) - - if len(ids) > 1: - cls.log.error( - "\"{}\" id mismatch to: {}".format( - instance_node, matches - ) - ) - invalid[instance_node] = matches - - instance.data["mismatched_output_ids"] = invalid - else: - invalid = instance.data["mismatched_output_ids"] - - return invalid - - @classmethod - def repair(cls, instance): - invalid_matches = cls.get_invalid_matches(instance) - - multiple_ids_match = [] - for instance_node, matches in invalid_matches.items(): - ids = set(get_id(node) for node in matches) - - # If there are multiple scene ids matched, an error needs to be - # raised for manual correction. - if len(ids) > 1: - multiple_ids_match.append({"node": instance_node, - "matches": matches}) - continue - - id_to_set = next(iter(ids)) - set_id(instance_node, id_to_set, overwrite=True) - - if multiple_ids_match: - raise PublishValidationError( - "Multiple matched ids found. Please repair manually: " - "{}".format(multiple_ids_match) - ) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index d3e01287e5..5e11227d68 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -1179,7 +1179,7 @@ "active": true }, "ValidateSkeletonTopGroupHierarchy": { - "enabled": false, + "enabled": true, "optional": true, "active": true }, 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 f2bbc0f70b..f4db51a079 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 @@ -829,7 +829,7 @@ }, { "key": "ValidateSkeletonRigContents", - "label": "ValidateSkeleton Rig Contents" + "label": "Validate Skeleton Rig Contents" }, { "key": "ValidateSkeletonRigControllers", diff --git a/server_addon/maya/server/settings/publishers.py b/server_addon/maya/server/settings/publishers.py index cb3af191a8..6c5baa3900 100644 --- a/server_addon/maya/server/settings/publishers.py +++ b/server_addon/maya/server/settings/publishers.py @@ -1234,7 +1234,7 @@ DEFAULT_PUBLISH_SETTINGS = { "active": True }, "ValidateSkeletonTopGroupHierarchy": { - "enabled": False, + "enabled": True, "optional": True, "active": True }, From 64f436a74dc69eb5506ecb9c986a426387b894ad Mon Sep 17 00:00:00 2001 From: Kayla Date: Sun, 1 Oct 2023 20:17:21 +0800 Subject: [PATCH 49/59] hound --- .../hosts/maya/plugins/publish/validate_rig_contents.py | 8 ++++---- .../maya/plugins/publish/validate_rig_controllers.py | 1 + .../hosts/maya/plugins/publish/validate_rig_output_ids.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index 5b8faf6cae..f3c2231b1f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -216,13 +216,13 @@ class ValidateSkeletonRigContents(ValidateRigContents): invalid_geometry = cls.validate_geometry(output_content) if invalid_hierarchy: cls.log.error("Found nodes which reside outside of root group " - "while they are set up for publishing." - "\n%s" % invalid_hierarchy) + "while they are set up for publishing." + "\n%s" % invalid_hierarchy) error = True if invalid_geometry: cls.log.error("Found nodes which reside outside of root group " - "while they are set up for publishing." - "\n%s" % invalid_hierarchy) + "while they are set up for publishing." + "\n%s" % invalid_hierarchy) error = True return error diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index c1e3d96bae..4e86e9859f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -228,6 +228,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): default = cls.CONTROLLER_DEFAULTS[attr] cls.log.info("Setting %s to %s" % (plug, default)) cmds.setAttr(plug, default) + @classmethod def get_node(cls, instance): return instance.data["rig_sets"].get("controls_SET") diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index e6204902f0..cd6ac511e2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -47,7 +47,7 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): invalid = {} if compute: - out_set = cls.get_node(instance) + out_set = cls.get_node(instance) if not out_set: instance.data["mismatched_output_ids"] = invalid return invalid From 0ddf5ffd90a996037bdaa8905e6fda1d37b4e08a Mon Sep 17 00:00:00 2001 From: Kayla Date: Mon, 2 Oct 2023 12:29:41 +0800 Subject: [PATCH 50/59] minor tweak & abstract some codes into functions in rig content --- openpype/hosts/maya/api/fbx.py | 2 +- .../plugins/publish/extract_fbx_animation.py | 4 +- .../plugins/publish/extract_skeleton_mesh.py | 2 +- .../plugins/publish/validate_rig_contents.py | 141 +++++++++++------- .../publish/validate_rig_controllers.py | 18 ++- .../publish/validate_rig_out_set_node_ids.py | 16 ++ .../publish/validate_rig_output_ids.py | 16 ++ 7 files changed, 136 insertions(+), 63 deletions(-) diff --git a/openpype/hosts/maya/api/fbx.py b/openpype/hosts/maya/api/fbx.py index 2dd4f5a73d..dbb3578f08 100644 --- a/openpype/hosts/maya/api/fbx.py +++ b/openpype/hosts/maya/api/fbx.py @@ -64,7 +64,7 @@ class FBXExtractor: "inputConnections": bool, "upAxis": str, # x, y or z, "triangulate": bool, - "FileVersion": str, + "fileVersion": str, "skeletonDefinitions": bool, "referencedAssetsContent": bool } diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 115ba39986..d67fca4e85 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -45,7 +45,7 @@ class ExtractFBXAnimation(publish.Extractor): # names as existing in the rig workfile namespace, relative_out_set = out_set_name.split(":", 1) cmds.namespace(relativeNames=True) - with namespaced(":" + namespace,new=False, relative_names=True) as namespace: # noqa + with namespaced(":" + namespace, new=False, relative_names=True) as namespace: # noqa fbx_exporter.export(relative_out_set, path) representations = instance.data.setdefault("representations", []) @@ -57,4 +57,4 @@ class ExtractFBXAnimation(publish.Extractor): }) self.log.debug( - "Extracted Fbx animation to: {0}".format(path)) + "Extracted FBX animation to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py index cecdf282e2..50c1fb3bde 100644 --- a/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py @@ -51,4 +51,4 @@ class ExtractSkeletonMesh(publish.Extractor, "stagingDir": staging_dir }) - self.log.debug("Extract animated FBX successful to: {0}".format(path)) + self.log.debug("Extract FBX to: {0}".format(path)) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index f3c2231b1f..c63d0e0a2e 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -35,26 +35,12 @@ class ValidateRigContents(pyblish.api.InstancePlugin): # Find required sets by suffix required, rig_sets = cls.get_nodes(instance) - missing = [ - key for key in required if key not in rig_sets - ] - if missing: - raise PublishValidationError( - "%s is missing sets: %s" % (instance, ", ".join(missing)) - ) + + cls.validate_missing_objectsets(instance, required, rig_sets) controls_set = rig_sets["controls_SET"] out_set = rig_sets["out_SET"] - # Ensure there are at least some transforms or dag nodes - # in the rig instance - set_members = instance.data['setMembers'] - if not cmds.ls(set_members, type="dagNode", long=True): - raise PublishValidationError( - "No dag nodes in the pointcache instance. " - "(Empty instance?)" - ) - # Ensure contents in sets and retrieve long path for all objects output_content = cmds.sets(out_set, query=True) or [] if not output_content: @@ -68,19 +54,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): ) controls_content = cmds.ls(controls_content, long=True) - # Validate members are inside the hierarchy from root node - root_nodes = cmds.ls(set_members, assemblies=True, long=True) - hierarchy = cmds.listRelatives(root_nodes, allDescendents=True, - fullPath=True) + root_nodes - hierarchy = set(hierarchy) - - invalid_hierarchy = [] - for node in output_content: - if node not in hierarchy: - invalid_hierarchy.append(node) - for node in controls_content: - if node not in hierarchy: - invalid_hierarchy.append(node) + rig_content = output_content + controls_content + invalid_hierarchy = cls.invalid_hierarchy(instance, rig_content) # Additional validations invalid_geometry = cls.validate_geometry(output_content) @@ -104,6 +79,62 @@ class ValidateRigContents(pyblish.api.InstancePlugin): error = True return error + @classmethod + def validate_missing_objectsets(cls, instance, + required_objsets, rig_sets): + """Validate missing objectsets in rig sets + + Args: + instance (str): instance + required_objsets (list): list of objectset names + rig_sets (list): list of rig sets + + Raises: + PublishValidationError: When the error is raised, it will show + which instance has the missing object sets + """ + missing = [ + key for key in required_objsets if key not in rig_sets + ] + if missing: + raise PublishValidationError( + "%s is missing sets: %s" % (instance, ", ".join(missing)) + ) + + @classmethod + def invalid_hierarchy(cls, instance, content): + """_summary_ + + Args: + instance (str): instance + content (list): list of content from rig sets + + Raises: + PublishValidationError: It means no dag nodes in + the rig instance + + Returns: + list: invalid hierarchy + """ + # Ensure there are at least some transforms or dag nodes + # in the rig instance + set_members = instance.data['setMembers'] + if not cmds.ls(set_members, type="dagNode", long=True): + raise PublishValidationError( + "No dag nodes in the rig instance. " + "(Empty instance?)" + ) + # Validate members are inside the hierarchy from root node + root_nodes = cmds.ls(set_members, assemblies=True, long=True) + hierarchy = cmds.listRelatives(root_nodes, allDescendents=True, + fullPath=True) + root_nodes + hierarchy = set(hierarchy) + invalid_hierarchy = [] + for node in content: + if node not in hierarchy: + invalid_hierarchy.append(node) + return invalid_hierarchy + @classmethod def validate_geometry(cls, set_members): """Check if the out set passes the validations @@ -130,8 +161,6 @@ class ValidateRigContents(pyblish.api.InstancePlugin): if cmds.nodeType(shape) not in cls.accepted_output: invalid.append(shape) - return invalid - @classmethod def validate_controls(cls, set_members): """Check if the controller set passes the validations @@ -157,6 +186,14 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def get_nodes(cls, instance): + """Get the target objectsets and rig sets nodes + + Args: + instance (str): instance + + Returns: + list: list of objectsets, list of rig sets nodes + """ objectsets = ["controls_SET", "out_SET"] rig_sets_nodes = instance.data.get("rig_sets", []) return objectsets, rig_sets_nodes @@ -181,39 +218,18 @@ class ValidateSkeletonRigContents(ValidateRigContents): @classmethod def get_invalid(cls, instance): objectsets, skeleton_mesh_nodes = cls.get_nodes(instance) - missing = [ - key for key in objectsets if key not in instance.data["rig_sets"] - ] - if missing: - cls.log.debug( - "%s is missing sets: %s" % (instance, ", ".join(missing)) - ) - return + cls.validate_missing_objectsets( + instance, objectsets, instance.data["rig_sets"]) - # Ensure there are at least some transforms or dag nodes - # in the rig instance - set_members = instance.data['setMembers'] - if not cmds.ls(set_members, type="dagNode", long=True): - raise PublishValidationError( - "No dag nodes in the pointcache instance. " - "(Empty instance?)" - ) # Ensure contents in sets and retrieve long path for all objects output_content = instance.data.get("skeleton_mesh", []) output_content = cmds.ls(skeleton_mesh_nodes, long=True) - # Validate members are inside the hierarchy from root node - root_nodes = cmds.ls(set_members, assemblies=True, long=True) - hierarchy = cmds.listRelatives(root_nodes, allDescendents=True, - fullPath=True) + root_nodes - hierarchy = set(hierarchy) + invalid_hierarchy = cls.invalid_hierarchy( + instance, output_content) + invalid_geometry = cls.validate_geometry(output_content) + error = False - invalid_hierarchy = [] - if output_content: - for node in output_content: - if node not in hierarchy: - invalid_hierarchy.append(node) - invalid_geometry = cls.validate_geometry(output_content) if invalid_hierarchy: cls.log.error("Found nodes which reside outside of root group " "while they are set up for publishing." @@ -228,6 +244,15 @@ class ValidateSkeletonRigContents(ValidateRigContents): @classmethod def get_nodes(cls, instance): + """Get the target objectsets and rig sets nodes + + Args: + instance (str): instance + + Returns: + list: list of objectsets, + list of objects node from skeletonMesh_SET + """ objectsets = ["skeletonMesh_SET"] skeleton_mesh_nodes = instance.data.get("skeleton_mesh", []) return objectsets, skeleton_mesh_nodes diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index 4e86e9859f..a10e2158fa 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -231,6 +231,14 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): @classmethod def get_node(cls, instance): + """Get target object nodes from controls_SET + + Args: + instance (str): instance + + Returns: + list: list of object nodes from controls_SET + """ return instance.data["rig_sets"].get("controls_SET") @@ -274,4 +282,12 @@ class ValidateSkeletonRigControllers(ValidateRigControllers): @classmethod def get_node(cls, instance): - return instance.data["rig_sets"].get("skeletonAnim_SET") + """Get target object nodes from skeletonMesh_SET + + Args: + instance (str): instance + + Returns: + list: list of object nodes from skeletonMesh_SET + """ + return instance.data["rig_sets"].get("skeletonMesh_SET") diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index 00eca608a1..6f713a3ca1 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -88,6 +88,14 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): @classmethod def get_node(cls, instance): + """Get target object nodes from out_SET + + Args: + instance (str): instance + + Returns: + list: list of object nodes from out_SET + """ return instance.data["rig_sets"].get("out_SET") @@ -114,5 +122,13 @@ class ValidateSkeletonRigOutSetNodeIds(ValidateRigOutSetNodeIds): @classmethod def get_node(cls, instance): + """Get target object nodes from skeletonMesh_SET + + Args: + instance (str): instance + + Returns: + list: list of object nodes from skeletonMesh_SET + """ return instance.data["rig_sets"].get( "skeletonMesh_SET") diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index cd6ac511e2..ec46b2be87 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -118,6 +118,14 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin): @classmethod def get_node(cls, instance): + """Get target object nodes from out_SET + + Args: + instance (str): instance + + Returns: + list: list of object nodes from out_SET + """ return instance.data["rig_sets"].get("out_SET") @@ -137,4 +145,12 @@ class ValidateSkeletonRigOutputIds(ValidateRigOutputIds): @classmethod def get_node(cls, instance): + """Get target object nodes from skeletonMesh_SET + + Args: + instance (str): instance + + Returns: + list: list of object nodes from skeletonMesh_SET + """ return instance.data["rig_sets"].get("skeletonMesh_SET") From 2850df81b9c73b6d9ffabebf7b8f9a28b5b9c959 Mon Sep 17 00:00:00 2001 From: Kayla Date: Mon, 2 Oct 2023 17:52:34 +0800 Subject: [PATCH 51/59] fix the over-indented of the namespace function under context manager --- openpype/hosts/maya/api/lib.py | 4 ++-- openpype/hosts/maya/plugins/publish/extract_fbx_animation.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index c05e375681..fb19cd64a6 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -938,8 +938,8 @@ def namespaced(namespace, new=True, relative_names=None): if new: namespace = unique_namespace(namespace) cmds.namespace(add=namespace) - if relative_names is not None: - cmds.namespace(relativeNames=relative_names) + if relative_names is not None: + cmds.namespace(relativeNames=relative_names) try: cmds.namespace(set=namespace) yield namespace diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index d67fca4e85..f9e696489e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -44,7 +44,6 @@ class ExtractFBXAnimation(publish.Extractor): # FBX does not include the namespace but preserves the node # names as existing in the rig workfile namespace, relative_out_set = out_set_name.split(":", 1) - cmds.namespace(relativeNames=True) with namespaced(":" + namespace, new=False, relative_names=True) as namespace: # noqa fbx_exporter.export(relative_out_set, path) From 4330281688177556995c3acac1e72057c8a3bce5 Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 13:53:30 +0800 Subject: [PATCH 52/59] small bugfix on collect skeleton mesh and minor tweak --- openpype/hosts/maya/api/lib.py | 8 ++--- .../plugins/publish/collect_skeleton_mesh.py | 34 ++++++++++--------- .../plugins/publish/extract_fbx_animation.py | 6 +++- .../publish/validate_animated_reference.py | 7 +++- .../plugins/publish/validate_rig_contents.py | 20 ++++++----- .../publish/validate_rig_controllers.py | 2 -- .../publish/validate_rig_out_set_node_ids.py | 5 --- .../publish/validate_rig_output_ids.py | 2 -- 8 files changed, 43 insertions(+), 41 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index fb19cd64a6..f62463420e 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4150,11 +4150,9 @@ def create_rig_animation_instance( host = registered_host() create_context = CreateContext(host) # Create the animation instance - rig_sets = [output, controls] - if anim_skeleton: - rig_sets.append(anim_skeleton) - if skeleton_mesh: - rig_sets.append(skeleton_mesh) + rig_sets = [output, controls, anim_skeleton, skeleton_mesh] + # Remove sets that this particular rig does not have + rig_sets = [s for s in rig_sets if s is not None] with maintained_selection(): cmds.select(rig_sets + roots, noExpand=True) create_context.create( diff --git a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index 648029c3fc..b7849238ae 100644 --- a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -12,13 +12,11 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): families = ["rig"] def process(self, instance): - skeleton_mesh_sets = [ - i for i in instance - if i.lower().endswith("skeletonmesh_set") - ] - if not skeleton_mesh_sets: + skeleton_mesh_set = instance.data["rig_sets"].get( + "skeletonMesh_SET") + if not skeleton_mesh_set: self.log.debug( - "skeletonMesh_SET found. " + "No skeletonMesh_SET found. " "Skipping collecting of skeleton mesh..." ) return @@ -30,14 +28,18 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): instance.data["skeleton_mesh"] = [] - if skeleton_mesh_sets: + if skeleton_mesh_set: + skeleton_mesh_content = cmds.sets( + skeleton_mesh_set, query=True) or [] + if not skeleton_mesh_content: + self.log.debug( + "No object nodes in skeletonMesh_SET. " + "Skipping collecting of skeleton mesh..." + ) + return instance.data["families"] += ["rig.fbx"] - for skeleton_mesh_set in skeleton_mesh_sets: - skeleton_mesh_content = cmds.sets( - skeleton_mesh_set, query=True) - if skeleton_mesh_content: - instance.data["skeleton_mesh"] += skeleton_mesh_content - self.log.debug( - "Collected skeletonmesh Set: {}".format( - skeleton_mesh_content - )) + instance.data["skeleton_mesh"] = skeleton_mesh_content + self.log.debug( + "Collected skeletonMesh_SET members: {}".format( + skeleton_mesh_content + )) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index f9e696489e..20352e1d8a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -44,7 +44,11 @@ class ExtractFBXAnimation(publish.Extractor): # FBX does not include the namespace but preserves the node # names as existing in the rig workfile namespace, relative_out_set = out_set_name.split(":", 1) - with namespaced(":" + namespace, new=False, relative_names=True) as namespace: # noqa + with namespaced( + ":" + namespace, + new=False, + relative_names=True + ) as namespace: fbx_exporter.export(relative_out_set, path) representations = instance.data.setdefault("representations", []) diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py index 3dc272d7cc..fe13561048 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -1,4 +1,5 @@ import pyblish.api +import openpype.hosts.maya.api.action from openpype.pipeline.publish import ( PublishValidationError, ValidateContentsOrder @@ -14,12 +15,15 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): families = ["animation.fbx"] label = "Animated Reference Rig" accepted_controllers = ["transform", "locator"] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): animated_sets = instance.data["animated_skeleton"] if not animated_sets: self.log.debug( - "No nodes found in skeletonAnim_SET.Skipping...") + "No nodes found in skeletonAnim_SET. " + "Skipping validation of animated reference rig..." + ) return for animated_reference in animated_sets: @@ -37,6 +41,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): " should be transforms" ) + @classmethod def validate_controls(self, set_members): """Check if the controller set passes the validations diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index c63d0e0a2e..c1a1ce4ffa 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -1,6 +1,6 @@ import pyblish.api from maya import cmds - +import openpype.hosts.maya.api.action from openpype.pipeline.publish import ( PublishValidationError, ValidateContentsOrder @@ -20,6 +20,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): label = "Rig Contents" hosts = ["maya"] families = ["rig"] + action = [openpype.hosts.maya.api.action.SelectInvalidAction ] accepted_output = ["mesh", "transform"] accepted_controllers = ["transform"] @@ -77,7 +78,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): cls.log.error("Only meshes can be part of the out_SET\n%s" % invalid_geometry) error = True - return error + if error: + return invalid_hierarchy + invalid_controls + invalid_geometry @classmethod def validate_missing_objectsets(cls, instance, @@ -103,7 +105,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def invalid_hierarchy(cls, instance, content): - """_summary_ + """Check if the sets passes the validation Args: instance (str): instance @@ -192,7 +194,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): instance (str): instance Returns: - list: list of objectsets, list of rig sets nodes + tuple: 2-tuple of list of objectsets, + list of rig sets nodes """ objectsets = ["controls_SET", "out_SET"] rig_sets_nodes = instance.data.get("rig_sets", []) @@ -213,8 +216,6 @@ class ValidateSkeletonRigContents(ValidateRigContents): hosts = ["maya"] families = ["rig.fbx"] - accepted_output = {"mesh", "transform", "locator"} - @classmethod def get_invalid(cls, instance): objectsets, skeleton_mesh_nodes = cls.get_nodes(instance) @@ -240,7 +241,8 @@ class ValidateSkeletonRigContents(ValidateRigContents): "while they are set up for publishing." "\n%s" % invalid_hierarchy) error = True - return error + if error: + return invalid_hierarchy + invalid_geometry @classmethod def get_nodes(cls, instance): @@ -250,8 +252,8 @@ class ValidateSkeletonRigContents(ValidateRigContents): instance (str): instance Returns: - list: list of objectsets, - list of objects node from skeletonMesh_SET + tuple: 2-tuple of list of objectsets, + list of rig sets nodes """ objectsets = ["skeletonMesh_SET"] skeleton_mesh_nodes = instance.data.get("skeleton_mesh", []) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py index a10e2158fa..82248c57b3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_controllers.py @@ -264,8 +264,6 @@ class ValidateSkeletonRigControllers(ValidateRigControllers): label = "Skeleton Rig Controllers" hosts = ["maya"] families = ["rig.fbx"] - actions = [RepairAction, - openpype.hosts.maya.api.action.SelectInvalidAction] # Default controller values CONTROLLER_DEFAULTS = { diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py index 6f713a3ca1..80ac0f27e6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_out_set_node_ids.py @@ -114,11 +114,6 @@ class ValidateSkeletonRigOutSetNodeIds(ValidateRigOutSetNodeIds): families = ["rig.fbx"] hosts = ['maya'] label = 'Skeleton Rig Out Set Node Ids' - actions = [ - openpype.hosts.maya.api.action.SelectInvalidAction, - RepairAction - ] - allow_history_only = False @classmethod def get_node(cls, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py index ec46b2be87..343d8e6924 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_output_ids.py @@ -140,8 +140,6 @@ class ValidateSkeletonRigOutputIds(ValidateRigOutputIds): label = "Skeleton Rig Output Ids" hosts = ["maya"] families = ["rig.fbx"] - actions = [RepairAction, - openpype.hosts.maya.api.action.SelectInvalidAction] @classmethod def get_node(cls, instance): From a49cacc74f4c89a4a70da4bfa8a6d2c0bf458d0a Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 13:54:37 +0800 Subject: [PATCH 53/59] hound --- openpype/hosts/maya/plugins/publish/validate_rig_contents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index c1a1ce4ffa..963ebcea83 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -20,7 +20,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): label = "Rig Contents" hosts = ["maya"] families = ["rig"] - action = [openpype.hosts.maya.api.action.SelectInvalidAction ] + action = [openpype.hosts.maya.api.action.SelectInvalidAction] accepted_output = ["mesh", "transform"] accepted_controllers = ["transform"] From ae1c98d10cc57d24252b373040a904772dc75ba4 Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 13:56:23 +0800 Subject: [PATCH 54/59] docstring edit for invalid hierarchy in validate rig content --- openpype/hosts/maya/plugins/publish/validate_rig_contents.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index 963ebcea83..f55365cc54 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -105,7 +105,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def invalid_hierarchy(cls, instance, content): - """Check if the sets passes the validation + """Check if the rig sets passes the validation with + correct hierarchy Args: instance (str): instance From 7cbe5e8f6259fae8134a108799a73a64ceb0a61a Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 15:39:48 +0800 Subject: [PATCH 55/59] docstring tweak and some code twek --- .../plugins/publish/collect_fbx_animation.py | 2 +- .../plugins/publish/collect_skeleton_mesh.py | 27 +++++++++---------- .../publish/validate_animated_reference.py | 2 +- .../plugins/publish/validate_rig_contents.py | 13 ++++----- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index ee5ac741c8..03a54af08a 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -19,7 +19,7 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin, return skeleton_sets = [ i for i in instance - if i.lower().endswith("skeletonanim_set") + if i.endswith("skeletonAnim_SET") ] if not skeleton_sets: return diff --git a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py index b7849238ae..31f0eca88c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py +++ b/openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py @@ -28,18 +28,17 @@ class CollectSkeletonMesh(pyblish.api.InstancePlugin): instance.data["skeleton_mesh"] = [] - if skeleton_mesh_set: - skeleton_mesh_content = cmds.sets( - skeleton_mesh_set, query=True) or [] - if not skeleton_mesh_content: - self.log.debug( - "No object nodes in skeletonMesh_SET. " - "Skipping collecting of skeleton mesh..." - ) - return - instance.data["families"] += ["rig.fbx"] - instance.data["skeleton_mesh"] = skeleton_mesh_content + skeleton_mesh_content = cmds.sets( + skeleton_mesh_set, query=True) or [] + if not skeleton_mesh_content: self.log.debug( - "Collected skeletonMesh_SET members: {}".format( - skeleton_mesh_content - )) + "No object nodes in skeletonMesh_SET. " + "Skipping collecting of skeleton mesh..." + ) + return + instance.data["families"] += ["rig.fbx"] + instance.data["skeleton_mesh"] = skeleton_mesh_content + self.log.debug( + "Collected skeletonMesh_SET members: {}".format( + skeleton_mesh_content + )) diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py index fe13561048..dd606ceaef 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -43,7 +43,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): @classmethod def validate_controls(self, set_members): - """Check if the controller set passes the validations + """Check if the controller set contains only accepted node types. Checks if all its set members are within the hierarchy of the root Checks if the node types of the set members valid diff --git a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py index f55365cc54..106b4024e2 100644 --- a/openpype/hosts/maya/plugins/publish/validate_rig_contents.py +++ b/openpype/hosts/maya/plugins/publish/validate_rig_contents.py @@ -105,8 +105,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def invalid_hierarchy(cls, instance, content): - """Check if the rig sets passes the validation with - correct hierarchy + """ + Check if all rig set members are within the hierarchy of the rig root Args: instance (str): instance @@ -140,9 +140,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def validate_geometry(cls, set_members): - """Check if the out set passes the validations - - Checks if all its set members are within the hierarchy of the root + """ Checks if the node types of the set members valid Args: @@ -166,9 +164,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): @classmethod def validate_controls(cls, set_members): - """Check if the controller set passes the validations - - Checks if all its set members are within the hierarchy of the root + """ + Checks if the control set members are allowed node types. Checks if the node types of the set members valid Args: From 3d2b0172859a8d5b5ab9d5e287bd38e8f6528311 Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 17:32:58 +0800 Subject: [PATCH 56/59] minor tweak --- openpype/hosts/maya/plugins/publish/collect_fbx_animation.py | 2 +- openpype/hosts/maya/plugins/publish/extract_fbx_animation.py | 3 +-- .../hosts/maya/plugins/publish/validate_animated_reference.py | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py index 03a54af08a..aef8765e9c 100644 --- a/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_fbx_animation.py @@ -33,4 +33,4 @@ class CollectFbxAnimation(pyblish.api.InstancePlugin, skeleton_content )) if skeleton_content: - instance.data["animated_skeleton"] += skeleton_content + instance.data["animated_skeleton"] = skeleton_content diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 20352e1d8a..27be724ec0 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -39,11 +39,10 @@ class ExtractFBXAnimation(publish.Extractor): fbx_exporter.set_options_from_instance(instance) - out_set_name = next(out for out in out_set) # Export from the rig's namespace so that the exported # FBX does not include the namespace but preserves the node # names as existing in the rig workfile - namespace, relative_out_set = out_set_name.split(":", 1) + namespace, relative_out_set = out_set[0].split(":", 1) with namespaced( ":" + namespace, new=False, diff --git a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py index dd606ceaef..4537892d6d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_animated_reference.py +++ b/openpype/hosts/maya/plugins/publish/validate_animated_reference.py @@ -18,7 +18,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): actions = [openpype.hosts.maya.api.action.SelectInvalidAction] def process(self, instance): - animated_sets = instance.data["animated_skeleton"] + animated_sets = instance.data.get("animated_skeleton", []) if not animated_sets: self.log.debug( "No nodes found in skeletonAnim_SET. " @@ -58,6 +58,7 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin): # Validate control types invalid = [] + set_members = cmds.ls(set_members, long=True) for node in set_members: if cmds.nodeType(node) not in self.accepted_controllers: invalid.append(node) From 12be0186b0b634fb60ee87cb0c9250a410d39b02 Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 17:40:38 +0800 Subject: [PATCH 57/59] minor tweak --- .../hosts/maya/plugins/publish/extract_fbx_animation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 27be724ec0..8036c799e7 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -31,7 +31,7 @@ class ExtractFBXAnimation(publish.Extractor): path = path.replace("\\", "/") fbx_exporter = fbx.FBXExtractor(log=self.log) - out_set = instance.data.get("animated_skeleton", []) + out_group = instance.data.get("animated_skeleton", []) # Export instance.data["constraints"] = True instance.data["skeletonDefinitions"] = True @@ -42,13 +42,13 @@ class ExtractFBXAnimation(publish.Extractor): # Export from the rig's namespace so that the exported # FBX does not include the namespace but preserves the node # names as existing in the rig workfile - namespace, relative_out_set = out_set[0].split(":", 1) + namespace, relative_out_group = out_group[0].split(":", 1) with namespaced( ":" + namespace, new=False, relative_names=True ) as namespace: - fbx_exporter.export(relative_out_set, path) + fbx_exporter.export(relative_out_group, path) representations = instance.data.setdefault("representations", []) representations.append({ From 71838b05153576235b969e915ac716fac88ce97a Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 20:06:14 +0800 Subject: [PATCH 58/59] abstract relativeNames namesapces into function --- openpype/hosts/maya/api/lib.py | 41 +++++++++++++++++++ .../plugins/publish/extract_fbx_animation.py | 15 ++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index f62463420e..1923a008d5 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -183,6 +183,47 @@ def maintained_selection(): cmds.select(clear=True) +def get_namespace(node): + """Return namespace of given node""" + return node.rsplit("|", 1)[-1].rsplit(":", 1)[0] + + +def strip_namespace(node, namespace): + """Strip given namespace from node path. + + The namespace will only be stripped from names + if it starts with that namespace. If the namespace + occurs within another namespace it's not removed. + + Examples: + >>> strip_namespace("namespace:node", namespace="namespace:") + "node" + >>> strip_namespace("hello:world:node", namespace="hello:world") + "node" + >>> strip_namespace("hello:world:node", namespace="hello") + "world:node" + >>> strip_namespace("hello:world:node", namespace="world") + "hello:world:node" + >>> strip_namespace("ns:group|ns:node", namespace="ns") + "group|node" + + Returns: + str: Node name without given starting namespace. + + """ + + # Ensure namespace ends with `:` + if not namespace.endswith(":"): + namespace = "{}:".format(namespace) + + # The long path for a node can also have the namespace + # in its parents so we need to remove it from each + return "|".join( + name[len(namespace):] if name.startswith(namespace) else name + for name in node.split("|") + ) + + def get_custom_namespace(custom_namespace): """Return unique namespace. diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py index 8036c799e7..8288bc9329 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx_animation.py @@ -6,7 +6,9 @@ import pyblish.api from openpype.pipeline import publish from openpype.hosts.maya.api import fbx -from openpype.hosts.maya.api.lib import namespaced +from openpype.hosts.maya.api.lib import ( + namespaced, get_namespace, strip_namespace +) class ExtractFBXAnimation(publish.Extractor): @@ -31,24 +33,25 @@ class ExtractFBXAnimation(publish.Extractor): path = path.replace("\\", "/") fbx_exporter = fbx.FBXExtractor(log=self.log) - out_group = instance.data.get("animated_skeleton", []) + out_members = instance.data.get("animated_skeleton", []) # Export instance.data["constraints"] = True instance.data["skeletonDefinitions"] = True instance.data["referencedAssetsContent"] = True - fbx_exporter.set_options_from_instance(instance) - # Export from the rig's namespace so that the exported # FBX does not include the namespace but preserves the node # names as existing in the rig workfile - namespace, relative_out_group = out_group[0].split(":", 1) + namespace = get_namespace(out_members[0]) + relative_out_members = [ + strip_namespace(node, namespace) for node in out_members + ] with namespaced( ":" + namespace, new=False, relative_names=True ) as namespace: - fbx_exporter.export(relative_out_group, path) + fbx_exporter.export(relative_out_members, path) representations = instance.data.setdefault("representations", []) representations.append({ From 8b16bacb5315377f7a6f2539b838ea32da0bacf6 Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 3 Oct 2023 20:21:37 +0800 Subject: [PATCH 59/59] make sure the get_namespace won't error out if it doesn't get anything --- openpype/hosts/maya/api/lib.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 1923a008d5..510d4ecc85 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -185,7 +185,11 @@ def maintained_selection(): def get_namespace(node): """Return namespace of given node""" - return node.rsplit("|", 1)[-1].rsplit(":", 1)[0] + node_name = node.rsplit("|", 1)[-1] + if ":" in node_name: + return node_name.rsplit(":", 1)[0] + else: + return "" def strip_namespace(node, namespace):