mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #5589 from ynput/enhancement/OP-6629_Maya-Export-Rig-Animation-as-FBX
Maya: Add optional Fbx extractors in Rig and Animation family
This commit is contained in:
commit
5338d33067
19 changed files with 928 additions and 106 deletions
|
|
@ -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:
|
||||
|
|
@ -53,7 +54,6 @@ class FBXExtractor:
|
|||
"bakeComplexEnd": int,
|
||||
"bakeComplexStep": int,
|
||||
"bakeResampleAnimation": bool,
|
||||
"animationOnly": bool,
|
||||
"useSceneName": bool,
|
||||
"quaternion": str, # "euler"
|
||||
"shapes": bool,
|
||||
|
|
@ -63,7 +63,10 @@ class FBXExtractor:
|
|||
"embeddedTextures": bool,
|
||||
"inputConnections": bool,
|
||||
"upAxis": str, # x, y or z,
|
||||
"triangulate": bool
|
||||
"triangulate": bool,
|
||||
"fileVersion": str,
|
||||
"skeletonDefinitions": bool,
|
||||
"referencedAssetsContent": bool
|
||||
}
|
||||
|
||||
@property
|
||||
|
|
@ -94,7 +97,6 @@ class FBXExtractor:
|
|||
"bakeComplexEnd": end_frame,
|
||||
"bakeComplexStep": 1,
|
||||
"bakeResampleAnimation": True,
|
||||
"animationOnly": False,
|
||||
"useSceneName": False,
|
||||
"quaternion": "euler",
|
||||
"shapes": True,
|
||||
|
|
@ -104,7 +106,10 @@ class FBXExtractor:
|
|||
"embeddedTextures": False,
|
||||
"inputConnections": True,
|
||||
"upAxis": "y",
|
||||
"triangulate": False
|
||||
"triangulate": False,
|
||||
"fileVersion": "FBX202000",
|
||||
"skeletonDefinitions": False,
|
||||
"referencedAssetsContent": False
|
||||
}
|
||||
|
||||
def __init__(self, log=None):
|
||||
|
|
@ -198,5 +203,9 @@ class FBXExtractor:
|
|||
path (str): Path to use for export.
|
||||
|
||||
"""
|
||||
cmds.select(members, r=True, noExpand=True)
|
||||
mel.eval('FBXExport -f "{}" -s'.format(path))
|
||||
# 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))
|
||||
|
|
|
|||
|
|
@ -183,6 +183,51 @@ def maintained_selection():
|
|||
cmds.select(clear=True)
|
||||
|
||||
|
||||
def get_namespace(node):
|
||||
"""Return namespace of given node"""
|
||||
node_name = node.rsplit("|", 1)[-1]
|
||||
if ":" in node_name:
|
||||
return node_name.rsplit(":", 1)[0]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -922,7 +967,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,15 +979,19 @@ 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)
|
||||
|
||||
if relative_names is not None:
|
||||
cmds.namespace(relativeNames=relative_names)
|
||||
try:
|
||||
cmds.namespace(set=namespace)
|
||||
yield namespace
|
||||
finally:
|
||||
cmds.namespace(set=original)
|
||||
if relative_names is not None:
|
||||
cmds.namespace(relativeNames=original_relative_names)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
@ -4100,14 +4149,19 @@ 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)
|
||||
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."
|
||||
|
||||
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)
|
||||
skeleton_mesh = next((node for node in nodes if
|
||||
node.endswith("skeletonMesh_SET")), None)
|
||||
|
||||
# Find the roots amongst the loaded nodes
|
||||
roots = (
|
||||
|
|
@ -4119,9 +4173,7 @@ 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": (
|
||||
context['subset']['data'].get('family') or
|
||||
|
|
@ -4142,10 +4194,12 @@ def create_rig_animation_instance(
|
|||
|
||||
host = registered_host()
|
||||
create_context = CreateContext(host)
|
||||
|
||||
# Create the animation instance
|
||||
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([output, controls] + roots, noExpand=True)
|
||||
cmds.select(rig_sets + roots, noExpand=True)
|
||||
create_context.create(
|
||||
creator_identifier=creator_identifier,
|
||||
variant=namespace,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,13 @@ 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)
|
||||
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)
|
||||
cmds.sets([controls, pointcache], forceElement=instance_node)
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,46 @@
|
|||
import openpype.hosts.maya.api.plugin
|
||||
import maya.cmds as cmds
|
||||
|
||||
|
||||
def _process_reference(file_url, name, namespace, options):
|
||||
"""Load files by referencing scene in Maya.
|
||||
|
||||
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):
|
||||
|
|
@ -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
|
||||
|
|
|
|||
36
openpype/hosts/maya/plugins/publish/collect_fbx_animation.py
Normal file
36
openpype/hosts/maya/plugins/publish/collect_fbx_animation.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from maya import cmds # noqa
|
||||
import pyblish.api
|
||||
from openpype.pipeline import OptionalPyblishPluginMixin
|
||||
|
||||
|
||||
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.endswith("skeletonAnim_SET")
|
||||
]
|
||||
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 skeleton data: {}".format(
|
||||
skeleton_content
|
||||
))
|
||||
if skeleton_content:
|
||||
instance.data["animated_skeleton"] = skeleton_content
|
||||
|
|
@ -22,7 +22,8 @@ class CollectRigSets(pyblish.api.InstancePlugin):
|
|||
def process(self, instance):
|
||||
|
||||
# Find required sets by suffix
|
||||
searching = {"controls_SET", "out_SET"}
|
||||
searching = {"controls_SET", "out_SET",
|
||||
"skeletonAnim_SET", "skeletonMesh_SET"}
|
||||
found = {}
|
||||
for node in cmds.ls(instance, exactType="objectSet"):
|
||||
for suffix in searching:
|
||||
|
|
|
|||
44
openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py
Normal file
44
openpype/hosts/maya/plugins/publish/collect_skeleton_mesh.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from maya import cmds # noqa
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectSkeletonMesh(pyblish.api.InstancePlugin):
|
||||
"""Collect Static Rig Data for FBX Extractor."""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.2
|
||||
label = "Collect Skeleton Mesh"
|
||||
hosts = ["maya"]
|
||||
families = ["rig"]
|
||||
|
||||
def process(self, instance):
|
||||
skeleton_mesh_set = instance.data["rig_sets"].get(
|
||||
"skeletonMesh_SET")
|
||||
if not skeleton_mesh_set:
|
||||
self.log.debug(
|
||||
"No 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
|
||||
|
||||
instance.data["skeleton_mesh"] = []
|
||||
|
||||
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
|
||||
self.log.debug(
|
||||
"Collected skeletonMesh_SET members: {}".format(
|
||||
skeleton_mesh_content
|
||||
))
|
||||
65
openpype/hosts/maya/plugins/publish/extract_fbx_animation.py
Normal file
65
openpype/hosts/maya/plugins/publish/extract_fbx_animation.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
from maya import cmds # noqa
|
||||
import pyblish.api
|
||||
|
||||
from openpype.pipeline import publish
|
||||
from openpype.hosts.maya.api import fbx
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
namespaced, get_namespace, strip_namespace
|
||||
)
|
||||
|
||||
|
||||
class ExtractFBXAnimation(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 Animation (FBX)"
|
||||
hosts = ["maya"]
|
||||
families = ["animation.fbx"]
|
||||
|
||||
def process(self, instance):
|
||||
# Define output path
|
||||
staging_dir = self.staging_dir(instance)
|
||||
filename = "{0}.fbx".format(instance.name)
|
||||
path = os.path.join(staging_dir, filename)
|
||||
path = path.replace("\\", "/")
|
||||
|
||||
fbx_exporter = fbx.FBXExtractor(log=self.log)
|
||||
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 = 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_members, path)
|
||||
|
||||
representations = instance.data.setdefault("representations", [])
|
||||
representations.append({
|
||||
'name': 'fbx',
|
||||
'ext': 'fbx',
|
||||
'files': filename,
|
||||
"stagingDir": staging_dir
|
||||
})
|
||||
|
||||
self.log.debug(
|
||||
"Extracted FBX animation to: {0}".format(path))
|
||||
54
openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py
Normal file
54
openpype/hosts/maya/plugins/publish/extract_skeleton_mesh.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
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 ExtractSkeletonMesh(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 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)
|
||||
|
||||
fbx_exporter = fbx.FBXExtractor(log=self.log)
|
||||
out_set = instance.data.get("skeleton_mesh", [])
|
||||
|
||||
instance.data["constraints"] = True
|
||||
instance.data["skeletonDefinitions"] = True
|
||||
|
||||
fbx_exporter.set_options_from_instance(instance)
|
||||
|
||||
# Export
|
||||
fbx_exporter.export(out_set, path)
|
||||
|
||||
representations = instance.data.setdefault("representations", [])
|
||||
representations.append({
|
||||
'name': 'fbx',
|
||||
'ext': 'fbx',
|
||||
'files': filename,
|
||||
"stagingDir": staging_dir
|
||||
})
|
||||
|
||||
self.log.debug("Extract FBX to: {0}".format(path))
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
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 nodes in skeletonAnim_SET are referenced"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
hosts = ["maya"]
|
||||
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.get("animated_skeleton", [])
|
||||
if not animated_sets:
|
||||
self.log.debug(
|
||||
"No nodes found in skeletonAnim_SET. "
|
||||
"Skipping validation of animated reference rig..."
|
||||
)
|
||||
return
|
||||
|
||||
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 skeletonAnim_SET"
|
||||
" should be referenced nodes"
|
||||
)
|
||||
invalid_controls = self.validate_controls(animated_sets)
|
||||
if invalid_controls:
|
||||
raise PublishValidationError(
|
||||
"All the content in skeletonAnim_SET"
|
||||
" should be transforms"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def validate_controls(self, set_members):
|
||||
"""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
|
||||
|
||||
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 = []
|
||||
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)
|
||||
|
||||
return invalid
|
||||
|
|
@ -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,33 +20,27 @@ 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"]
|
||||
|
||||
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"]
|
||||
missing = [
|
||||
key for key in required if key not in instance.data["rig_sets"]
|
||||
]
|
||||
if missing:
|
||||
raise PublishValidationError(
|
||||
"%s is missing sets: %s" % (instance, ", ".join(missing))
|
||||
)
|
||||
required, rig_sets = cls.get_nodes(instance)
|
||||
|
||||
controls_set = instance.data["rig_sets"]["controls_SET"]
|
||||
out_set = instance.data["rig_sets"]["out_SET"]
|
||||
cls.validate_missing_objectsets(instance, required, 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?)"
|
||||
)
|
||||
controls_set = rig_sets["controls_SET"]
|
||||
out_set = rig_sets["out_SET"]
|
||||
|
||||
# Ensure contents in sets and retrieve long path for all objects
|
||||
output_content = cmds.sets(out_set, query=True) or []
|
||||
|
|
@ -61,49 +55,92 @@ 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 = 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
|
||||
|
||||
if error:
|
||||
return invalid_hierarchy + invalid_controls + invalid_geometry
|
||||
|
||||
@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(
|
||||
"Invalid rig content. See log for details.")
|
||||
"%s is missing sets: %s" % (instance, ", ".join(missing))
|
||||
)
|
||||
|
||||
def validate_geometry(self, set_members):
|
||||
"""Check if the out set passes the validations
|
||||
@classmethod
|
||||
def invalid_hierarchy(cls, instance, content):
|
||||
"""
|
||||
Check if all rig set members are within the hierarchy of the rig root
|
||||
|
||||
Checks if all its set members are within the hierarchy of the root
|
||||
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):
|
||||
"""
|
||||
Checks if the node types of the set members valid
|
||||
|
||||
Args:
|
||||
|
|
@ -122,15 +159,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):
|
||||
"""Check if the controller set passes the validations
|
||||
|
||||
Checks if all its set members are within the hierarchy of the root
|
||||
@classmethod
|
||||
def validate_controls(cls, set_members):
|
||||
"""
|
||||
Checks if the control set members are allowed node types.
|
||||
Checks if the node types of the set members valid
|
||||
|
||||
Args:
|
||||
|
|
@ -144,7 +179,80 @@ 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):
|
||||
"""Get the target objectsets and rig sets nodes
|
||||
|
||||
Args:
|
||||
instance (str): instance
|
||||
|
||||
Returns:
|
||||
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", [])
|
||||
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"]
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
objectsets, skeleton_mesh_nodes = cls.get_nodes(instance)
|
||||
cls.validate_missing_objectsets(
|
||||
instance, objectsets, instance.data["rig_sets"])
|
||||
|
||||
# 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)
|
||||
|
||||
invalid_hierarchy = cls.invalid_hierarchy(
|
||||
instance, output_content)
|
||||
invalid_geometry = cls.validate_geometry(output_content)
|
||||
|
||||
error = False
|
||||
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
|
||||
if error:
|
||||
return invalid_hierarchy + invalid_geometry
|
||||
|
||||
@classmethod
|
||||
def get_nodes(cls, instance):
|
||||
"""Get the target objectsets and rig sets nodes
|
||||
|
||||
Args:
|
||||
instance (str): instance
|
||||
|
||||
Returns:
|
||||
tuple: 2-tuple of list of objectsets,
|
||||
list of rig sets nodes
|
||||
"""
|
||||
objectsets = ["skeletonMesh_SET"]
|
||||
skeleton_mesh_nodes = instance.data.get("skeleton_mesh", [])
|
||||
return objectsets, skeleton_mesh_nodes
|
||||
|
|
|
|||
|
|
@ -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,64 @@ 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):
|
||||
"""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")
|
||||
|
||||
|
||||
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"]
|
||||
|
||||
# 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):
|
||||
"""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")
|
||||
|
|
|
|||
|
|
@ -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,45 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin):
|
|||
continue
|
||||
|
||||
lib.set_id(node, sibling_id, overwrite=True)
|
||||
|
||||
@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")
|
||||
|
||||
|
||||
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'
|
||||
|
||||
@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")
|
||||
|
|
|
|||
|
|
@ -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,40 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin):
|
|||
"Multiple matched ids found. Please repair manually: "
|
||||
"{}".format(multiple_ids_match)
|
||||
)
|
||||
|
||||
@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")
|
||||
|
||||
|
||||
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"]
|
||||
|
||||
@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")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# -*- 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 = "Skeleton Rig Top Group Hierarchy"
|
||||
families = ["rig.fbx"]
|
||||
|
||||
def process(self, instance):
|
||||
invalid = []
|
||||
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 "
|
||||
"is not at the top hierarchy: {}".format(invalid))
|
||||
|
||||
def get_top_hierarchy(self, targets):
|
||||
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
|
||||
|
|
@ -707,6 +707,9 @@
|
|||
"CollectMayaRender": {
|
||||
"sync_workfile_version": false
|
||||
},
|
||||
"CollectFbxAnimation": {
|
||||
"enabled": true
|
||||
},
|
||||
"CollectFbxCamera": {
|
||||
"enabled": false
|
||||
},
|
||||
|
|
@ -1120,6 +1123,11 @@
|
|||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateAnimatedReferenceRig": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
"active": true
|
||||
},
|
||||
"ValidateAnimationContent": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
|
|
@ -1140,6 +1148,16 @@
|
|||
"optional": false,
|
||||
"active": true
|
||||
},
|
||||
"ValidateSkeletonRigContents": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateSkeletonRigControllers": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateSkinclusterDeformerSet": {
|
||||
"enabled": true,
|
||||
"optional": false,
|
||||
|
|
@ -1150,6 +1168,21 @@
|
|||
"optional": false,
|
||||
"allow_history_only": false
|
||||
},
|
||||
"ValidateSkeletonRigOutSetNodeIds": {
|
||||
"enabled": false,
|
||||
"optional": false,
|
||||
"allow_history_only": false
|
||||
},
|
||||
"ValidateSkeletonRigOutputIds": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateSkeletonTopGroupHierarchy": {
|
||||
"enabled": true,
|
||||
"optional": true,
|
||||
"active": true
|
||||
},
|
||||
"ValidateCameraAttributes": {
|
||||
"enabled": false,
|
||||
"optional": true,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -793,6 +807,10 @@
|
|||
"key": "ValidateRigControllers",
|
||||
"label": "Validate Rig Controllers"
|
||||
},
|
||||
{
|
||||
"key": "ValidateAnimatedReferenceRig",
|
||||
"label": "Validate Animated Reference Rig"
|
||||
},
|
||||
{
|
||||
"key": "ValidateAnimationContent",
|
||||
"label": "Validate Animation Content"
|
||||
|
|
@ -809,9 +827,51 @@
|
|||
"key": "ValidateSkeletalMeshHierarchy",
|
||||
"label": "Validate Skeletal Mesh Top Node"
|
||||
},
|
||||
{
|
||||
{
|
||||
"key": "ValidateSkeletonRigContents",
|
||||
"label": "Validate Skeleton Rig Contents"
|
||||
},
|
||||
{
|
||||
"key": "ValidateSkeletonRigControllers",
|
||||
"label": "Validate Skeleton Rig Controllers"
|
||||
},
|
||||
{
|
||||
"key": "ValidateSkinclusterDeformerSet",
|
||||
"label": "Validate Skincluster Deformer Relationships"
|
||||
},
|
||||
{
|
||||
"key": "ValidateSkeletonRigOutputIds",
|
||||
"label": "Validate Skeleton Rig Output Ids"
|
||||
},
|
||||
{
|
||||
"key": "ValidateSkeletonTopGroupHierarchy",
|
||||
"label": "Validate Skeleton Top Group Hierarchy"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"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 +879,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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -644,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",
|
||||
|
|
@ -660,14 +672,34 @@ 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"
|
||||
)
|
||||
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",
|
||||
)
|
||||
ValidateSkeletonRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field(
|
||||
default_factory=ValidateRigOutSetNodeIdsModel,
|
||||
title="Validate Skeleton Rig Out Set Node Ids",
|
||||
)
|
||||
# Rig - END
|
||||
ValidateCameraAttributes: BasicValidateModel = Field(
|
||||
default_factory=BasicValidateModel,
|
||||
|
|
@ -748,6 +780,9 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"CollectMayaRender": {
|
||||
"sync_workfile_version": False
|
||||
},
|
||||
"CollectFbxAnimation": {
|
||||
"enabled": True
|
||||
},
|
||||
"CollectFbxCamera": {
|
||||
"enabled": False
|
||||
},
|
||||
|
|
@ -1143,6 +1178,11 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateAnimatedReferenceRig": {
|
||||
"enabled": True,
|
||||
"optional": False,
|
||||
"active": True
|
||||
},
|
||||
"ValidateAnimationContent": {
|
||||
"enabled": True,
|
||||
"optional": False,
|
||||
|
|
@ -1163,6 +1203,16 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"optional": False,
|
||||
"active": True
|
||||
},
|
||||
"ValidateSkeletonRigContents": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateSkeletonRigControllers": {
|
||||
"enabled": False,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateSkinclusterDeformerSet": {
|
||||
"enabled": True,
|
||||
"optional": False,
|
||||
|
|
@ -1173,6 +1223,21 @@ 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
|
||||
},
|
||||
"ValidateSkeletonTopGroupHierarchy": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
"active": True
|
||||
},
|
||||
"ValidateCameraAttributes": {
|
||||
"enabled": False,
|
||||
"optional": True,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring addon version."""
|
||||
__version__ = "0.1.3"
|
||||
__version__ = "0.1.4"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue