mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 05:42:15 +01:00
Draft for implementing a native Maya USD creator next to the Multiverse USD creator
This commit is contained in:
parent
8aee7d0ca4
commit
8ceedd7b60
6 changed files with 451 additions and 1 deletions
|
|
@ -129,12 +129,34 @@ class MayaCreatorBase(object):
|
|||
shared_data["maya_cached_legacy_subsets"] = cache_legacy
|
||||
return shared_data
|
||||
|
||||
def get_publish_families(self):
|
||||
"""Return families for the instances of this creator.
|
||||
|
||||
Allow a Creator to define multiple families so that a creator can
|
||||
e.g. specify `usd` and `usdMaya` and another USD creator can also
|
||||
specify `usd` but apply different extractors like `usdMultiverse`.
|
||||
|
||||
There is no need to override this method if you only have the
|
||||
primary family defined by the `family` property as that will always
|
||||
be set.
|
||||
|
||||
Returns:
|
||||
list: families for instances of this creator
|
||||
|
||||
"""
|
||||
return []
|
||||
|
||||
def imprint_instance_node(self, node, data):
|
||||
|
||||
# We never store the instance_node as value on the node since
|
||||
# it's the node name itself
|
||||
data.pop("instance_node", None)
|
||||
|
||||
# Don't store `families` since it's up to the creator itself
|
||||
# to define the initial publish families - not a stored attribute of
|
||||
# `families`
|
||||
data.pop("families", None)
|
||||
|
||||
# We store creator attributes at the root level and assume they
|
||||
# will not clash in names with `subset`, `task`, etc. and other
|
||||
# default names. This is just so these attributes in many cases
|
||||
|
|
@ -186,6 +208,11 @@ class MayaCreatorBase(object):
|
|||
# Explicitly re-parse the node name
|
||||
node_data["instance_node"] = node
|
||||
|
||||
# If the creator plug-in specifies
|
||||
families = self.get_publish_families()
|
||||
if families:
|
||||
node_data["families"] = families
|
||||
|
||||
return node_data
|
||||
|
||||
def _default_collect_instances(self):
|
||||
|
|
@ -230,6 +257,14 @@ class MayaCreator(NewCreator, MayaCreatorBase):
|
|||
if pre_create_data.get("use_selection"):
|
||||
members = cmds.ls(selection=True)
|
||||
|
||||
# Allow a Creator to define multiple families
|
||||
publish_families = self.get_publish_families()
|
||||
if publish_families:
|
||||
families = instance_data.setdefault("families", [])
|
||||
for family in self.get_publish_families():
|
||||
if family not in families:
|
||||
families.append(family)
|
||||
|
||||
with lib.undo_chunk():
|
||||
instance_node = cmds.sets(members, name=subset_name)
|
||||
instance_data["instance_node"] = instance_node
|
||||
|
|
|
|||
143
openpype/hosts/maya/plugins/create/create_maya_usd.py
Normal file
143
openpype/hosts/maya/plugins/create/create_maya_usd.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
from openpype.hosts.maya.api import plugin, lib
|
||||
from openpype.lib import (
|
||||
BoolDef,
|
||||
NumberDef,
|
||||
TextDef,
|
||||
EnumDef
|
||||
)
|
||||
|
||||
|
||||
class CreateMayaUsd(plugin.MayaCreator):
|
||||
"""Create Maya USD Export"""
|
||||
|
||||
identifier = "io.openpype.creators.maya.mayausd"
|
||||
label = "Maya USD"
|
||||
family = "usd"
|
||||
icon = "cubes"
|
||||
description = "Create Maya USD Export"
|
||||
|
||||
def get_publish_families(self):
|
||||
return ["usd", "mayaUsd"]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
|
||||
defs = lib.collect_animation_defs()
|
||||
defs.extend([
|
||||
EnumDef("defaultUSDFormat",
|
||||
label="File format",
|
||||
items={
|
||||
"usdc": "Binary",
|
||||
"usda": "ASCII"
|
||||
},
|
||||
default="usdc"),
|
||||
BoolDef("stripNamespaces",
|
||||
label="Strip Namespaces",
|
||||
default=True),
|
||||
BoolDef("mergeTransformAndShape",
|
||||
label="Merge Transform and Shape",
|
||||
default=True),
|
||||
# BoolDef("writeAncestors",
|
||||
# label="Write Ancestors",
|
||||
# default=True),
|
||||
# BoolDef("flattenParentXforms",
|
||||
# label="Flatten Parent Xforms",
|
||||
# default=False),
|
||||
# BoolDef("writeSparseOverrides",
|
||||
# label="Write Sparse Overrides",
|
||||
# default=False),
|
||||
# BoolDef("useMetaPrimPath",
|
||||
# label="Use Meta Prim Path",
|
||||
# default=False),
|
||||
# TextDef("customRootPath",
|
||||
# label="Custom Root Path",
|
||||
# default=''),
|
||||
# TextDef("customAttributes",
|
||||
# label="Custom Attributes",
|
||||
# tooltip="Comma-separated list of attribute names",
|
||||
# default=''),
|
||||
# TextDef("nodeTypesToIgnore",
|
||||
# label="Node Types to Ignore",
|
||||
# tooltip="Comma-separated list of node types to be ignored",
|
||||
# default=''),
|
||||
# BoolDef("writeMeshes",
|
||||
# label="Write Meshes",
|
||||
# default=True),
|
||||
# BoolDef("writeCurves",
|
||||
# label="Write Curves",
|
||||
# default=True),
|
||||
# BoolDef("writeParticles",
|
||||
# label="Write Particles",
|
||||
# default=True),
|
||||
# BoolDef("writeCameras",
|
||||
# label="Write Cameras",
|
||||
# default=False),
|
||||
# BoolDef("writeLights",
|
||||
# label="Write Lights",
|
||||
# default=False),
|
||||
# BoolDef("writeJoints",
|
||||
# label="Write Joints",
|
||||
# default=False),
|
||||
# BoolDef("writeCollections",
|
||||
# label="Write Collections",
|
||||
# default=False),
|
||||
# BoolDef("writePositions",
|
||||
# label="Write Positions",
|
||||
# default=True),
|
||||
# BoolDef("writeNormals",
|
||||
# label="Write Normals",
|
||||
# default=True),
|
||||
# BoolDef("writeUVs",
|
||||
# label="Write UVs",
|
||||
# default=True),
|
||||
# BoolDef("writeColorSets",
|
||||
# label="Write Color Sets",
|
||||
# default=False),
|
||||
# BoolDef("writeTangents",
|
||||
# label="Write Tangents",
|
||||
# default=False),
|
||||
# BoolDef("writeRefPositions",
|
||||
# label="Write Ref Positions",
|
||||
# default=True),
|
||||
# BoolDef("writeBlendShapes",
|
||||
# label="Write BlendShapes",
|
||||
# default=False),
|
||||
# BoolDef("writeDisplayColor",
|
||||
# label="Write Display Color",
|
||||
# default=True),
|
||||
# BoolDef("writeSkinWeights",
|
||||
# label="Write Skin Weights",
|
||||
# default=False),
|
||||
# BoolDef("writeMaterialAssignment",
|
||||
# label="Write Material Assignment",
|
||||
# default=False),
|
||||
# BoolDef("writeHardwareShader",
|
||||
# label="Write Hardware Shader",
|
||||
# default=False),
|
||||
# BoolDef("writeShadingNetworks",
|
||||
# label="Write Shading Networks",
|
||||
# default=False),
|
||||
# BoolDef("writeTransformMatrix",
|
||||
# label="Write Transform Matrix",
|
||||
# default=True),
|
||||
# BoolDef("writeUsdAttributes",
|
||||
# label="Write USD Attributes",
|
||||
# default=True),
|
||||
# BoolDef("writeInstancesAsReferences",
|
||||
# label="Write Instances as References",
|
||||
# default=False),
|
||||
# BoolDef("timeVaryingTopology",
|
||||
# label="Time Varying Topology",
|
||||
# default=False),
|
||||
# TextDef("customMaterialNamespace",
|
||||
# label="Custom Material Namespace",
|
||||
# default=''),
|
||||
# NumberDef("numTimeSamples",
|
||||
# label="Num Time Samples",
|
||||
# default=1),
|
||||
# NumberDef("timeSamplesSpan",
|
||||
# label="Time Samples Span",
|
||||
# default=0.0),
|
||||
#
|
||||
])
|
||||
|
||||
return defs
|
||||
|
|
@ -14,6 +14,10 @@ class CreateMultiverseUsd(plugin.MayaCreator):
|
|||
label = "Multiverse USD Asset"
|
||||
family = "usd"
|
||||
icon = "cubes"
|
||||
description = "Create Multiverse USD Asset"
|
||||
|
||||
def get_publish_families(self):
|
||||
return ["usd", "mvUsd"]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
|
||||
|
|
|
|||
100
openpype/hosts/maya/plugins/load/load_maya_usd.py
Normal file
100
openpype/hosts/maya/plugins/load/load_maya_usd.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import maya.cmds as cmds
|
||||
|
||||
from openpype.pipeline import (
|
||||
load,
|
||||
get_representation_path,
|
||||
)
|
||||
from openpype.pipeline.load import get_representation_path_from_context
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
namespaced,
|
||||
unique_namespace
|
||||
)
|
||||
from openpype.hosts.maya.api.pipeline import containerise
|
||||
|
||||
|
||||
class MayaUsdLoader(load.LoaderPlugin):
|
||||
"""Read USD data in a Maya USD Proxy"""
|
||||
|
||||
families = ["model", "usd", "pointcache", "animation"]
|
||||
representations = ["usd", "usda", "usdc", "usdz", "abc"]
|
||||
|
||||
label = "Load USD to Maya Proxy"
|
||||
order = -1
|
||||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
def load(self, context, name=None, namespace=None, options=None):
|
||||
asset = context['asset']['name']
|
||||
namespace = namespace or unique_namespace(
|
||||
asset + "_",
|
||||
prefix="_" if asset[0].isdigit() else "",
|
||||
suffix="_",
|
||||
)
|
||||
|
||||
# Make sure we can load the plugin
|
||||
cmds.loadPlugin("mayaUsdPlugin", quiet=True)
|
||||
|
||||
path = get_representation_path_from_context(context)
|
||||
|
||||
# Create the shape
|
||||
cmds.namespace(addNamespace=namespace)
|
||||
with namespaced(namespace, new=False):
|
||||
transform = cmds.createNode("transform",
|
||||
name=name,
|
||||
skipSelect=True)
|
||||
proxy = cmds.createNode('mayaUsdProxyShape',
|
||||
name="{}Shape".format(name),
|
||||
parent=transform,
|
||||
skipSelect=True)
|
||||
|
||||
cmds.connectAttr("time1.outTime", "{}.time".format(proxy))
|
||||
cmds.setAttr("{}.filePath".format(proxy), path, type="string")
|
||||
|
||||
nodes = [transform, proxy]
|
||||
self[:] = nodes
|
||||
|
||||
return containerise(
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
nodes=nodes,
|
||||
context=context,
|
||||
loader=self.__class__.__name__)
|
||||
|
||||
def update(self, container, representation):
|
||||
# type: (dict, dict) -> None
|
||||
"""Update container with specified representation."""
|
||||
node = container['objectName']
|
||||
assert cmds.objExists(node), "Missing container"
|
||||
|
||||
members = cmds.sets(node, query=True) or []
|
||||
shapes = cmds.ls(members, type="mayaUsdProxyShape")
|
||||
|
||||
path = get_representation_path(representation)
|
||||
for shape in shapes:
|
||||
cmds.setAttr("{}.filePath".format(shape), path, type="string")
|
||||
|
||||
cmds.setAttr("{}.representation".format(node),
|
||||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def remove(self, container):
|
||||
# type: (dict) -> None
|
||||
"""Remove loaded container."""
|
||||
# Delete container and its contents
|
||||
if cmds.objExists(container['objectName']):
|
||||
members = cmds.sets(container['objectName'], query=True) or []
|
||||
cmds.delete([container['objectName']] + members)
|
||||
|
||||
# Remove the namespace, if empty
|
||||
namespace = container['namespace']
|
||||
if cmds.namespace(exists=namespace):
|
||||
members = cmds.namespaceInfo(namespace, listNamespace=True)
|
||||
if not members:
|
||||
cmds.namespace(removeNamespace=namespace)
|
||||
else:
|
||||
self.log.warning("Namespace not deleted because it "
|
||||
"still has members: %s", namespace)
|
||||
168
openpype/hosts/maya/plugins/publish/extract_maya_usd.py
Normal file
168
openpype/hosts/maya/plugins/publish/extract_maya_usd.py
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import os
|
||||
import six
|
||||
|
||||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
from openpype.pipeline import publish
|
||||
from openpype.hosts.maya.api.lib import maintained_selection
|
||||
|
||||
|
||||
class ExtractMayaUsd(publish.Extractor):
|
||||
"""Extractor for Maya USD Asset data.
|
||||
|
||||
Upon publish a .usd (or .usdz) asset file will typically be written.
|
||||
"""
|
||||
|
||||
label = "Extract Maya USD Asset"
|
||||
hosts = ["maya"]
|
||||
families = ["mayaUsd"]
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Overridable options for Maya USD Export
|
||||
|
||||
Given in the following format
|
||||
- {NAME: EXPECTED TYPE}
|
||||
|
||||
If the overridden option's type does not match,
|
||||
the option is not included and a warning is logged.
|
||||
|
||||
"""
|
||||
|
||||
# TODO: Support more `mayaUSDExport` parameters
|
||||
return {
|
||||
"stripNamespaces": bool,
|
||||
"mergeTransformAndShape": bool,
|
||||
"exportDisplayColor": bool,
|
||||
"exportColorSets": bool,
|
||||
"exportInstances": bool,
|
||||
"exportUVs": bool,
|
||||
"exportVisibility": bool,
|
||||
"exportComponentTags": bool,
|
||||
"exportRefsAsInstanceable": bool,
|
||||
"eulerFilter": bool,
|
||||
"renderableOnly": bool,
|
||||
#"worldspace": bool,
|
||||
}
|
||||
|
||||
@property
|
||||
def default_options(self):
|
||||
"""The default options for Maya USD Export."""
|
||||
|
||||
# TODO: Support more `mayaUSDExport` parameters
|
||||
return {
|
||||
"stripNamespaces": False,
|
||||
"mergeTransformAndShape": False,
|
||||
"exportDisplayColor": False,
|
||||
"exportColorSets": True,
|
||||
"exportInstances": True,
|
||||
"exportUVs": True,
|
||||
"exportVisibility": True,
|
||||
"exportComponentTags": True,
|
||||
"exportRefsAsInstanceable": False,
|
||||
"eulerFilter": True,
|
||||
"renderableOnly": False,
|
||||
#"worldspace": False
|
||||
}
|
||||
|
||||
def parse_overrides(self, instance, options):
|
||||
"""Inspect data of instance to determine overridden options"""
|
||||
|
||||
for key in instance.data:
|
||||
if key not in self.options:
|
||||
continue
|
||||
|
||||
# Ensure the data is of correct type
|
||||
value = instance.data[key]
|
||||
if isinstance(value, six.text_type):
|
||||
value = str(value)
|
||||
if not isinstance(value, self.options[key]):
|
||||
self.log.warning(
|
||||
"Overridden attribute {key} was of "
|
||||
"the wrong type: {invalid_type} "
|
||||
"- should have been {valid_type}".format(
|
||||
key=key,
|
||||
invalid_type=type(value).__name__,
|
||||
valid_type=self.options[key].__name__))
|
||||
continue
|
||||
|
||||
options[key] = value
|
||||
|
||||
return options
|
||||
|
||||
def filter_members(self, members):
|
||||
# Can be overridden by inherited classes
|
||||
return members
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
# Load plugin first
|
||||
cmds.loadPlugin("mayaUsdPlugin", quiet=True)
|
||||
|
||||
# Define output file path
|
||||
staging_dir = self.staging_dir(instance)
|
||||
file_name = "{0}.usd".format(instance.name)
|
||||
file_path = os.path.join(staging_dir, file_name)
|
||||
file_path = file_path.replace('\\', '/')
|
||||
|
||||
# Parse export options
|
||||
options = self.default_options
|
||||
options = self.parse_overrides(instance, options)
|
||||
self.log.info("Export options: {0}".format(options))
|
||||
|
||||
# Perform extraction
|
||||
self.log.debug("Performing extraction ...")
|
||||
|
||||
members = instance.data("setMembers")
|
||||
self.log.debug('Collected objects: {}'.format(members))
|
||||
members = self.filter_members(members)
|
||||
if not members:
|
||||
self.log.error('No members!')
|
||||
return
|
||||
|
||||
start = instance.data["frameStartHandle"]
|
||||
end = instance.data["frameEndHandle"]
|
||||
|
||||
with maintained_selection():
|
||||
self.log.debug('Exporting USD: {} / {}'.format(file_path, members))
|
||||
cmds.mayaUSDExport(file=file_path,
|
||||
frameRange=(start, end),
|
||||
frameStride=instance.data.get("step", 1.0),
|
||||
exportRoots=members,
|
||||
**options)
|
||||
|
||||
representation = {
|
||||
'name': "usd",
|
||||
'ext': "usd",
|
||||
'files': file_name,
|
||||
'stagingDir': staging_dir
|
||||
}
|
||||
instance.data.setdefault("representations", []).append(representation)
|
||||
|
||||
self.log.debug(
|
||||
"Extracted instance {} to {}".format(instance.name, file_path)
|
||||
)
|
||||
|
||||
|
||||
class ExtractMayaUsdAnim(ExtractMayaUsd):
|
||||
"""Extractor for Maya USD Animation Sparse Cache data.
|
||||
|
||||
This will extract the sparse cache data from the scene and generate a
|
||||
USD file with all the animation data.
|
||||
|
||||
Upon publish a .usd sparse cache will be written.
|
||||
"""
|
||||
label = "Extract Maya USD Animation Sparse Cache"
|
||||
families = ["animation", "mayaUsd"]
|
||||
match = pyblish.api.Subset
|
||||
|
||||
def filter_members(self, members):
|
||||
out_set = next((i for i in members if i.endswith("out_SET")), None)
|
||||
|
||||
if out_set is None:
|
||||
self.log.warning("Expecting out_SET")
|
||||
return None
|
||||
|
||||
members = cmds.ls(cmds.sets(out_set, query=True), long=True)
|
||||
return members
|
||||
|
|
@ -28,7 +28,7 @@ class ExtractMultiverseUsd(publish.Extractor):
|
|||
|
||||
label = "Extract Multiverse USD Asset"
|
||||
hosts = ["maya"]
|
||||
families = ["usd"]
|
||||
families = ["mvUsd"]
|
||||
scene_type = "usd"
|
||||
file_formats = ["usd", "usda", "usdz"]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue