Skip to content

Instantly share code, notes, and snippets.

@BigRoy
Last active February 6, 2024 20:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BigRoy/7d73ec1bfcc5944a6aa887ee31b1bf44 to your computer and use it in GitHub Desktop.
Save BigRoy/7d73ec1bfcc5944a6aa887ee31b1bf44 to your computer and use it in GitHub Desktop.
Maya USD Export Chaser to keep only 'lookdev' related specs in the output file, to make a "look" overlay USD file from native maya geometry export - using Arnold job context in this case, to export materials and e.g. arnold subdiv opinions only
import logging
from maya.api import OpenMaya
import mayaUsd.lib as mayaUsdLib
from pxr import Sdf
def log_errors(fn):
"""Decorator to log errors on error"""
def wrap(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception as exc:
logging.error(exc, exc_info=True)
raise
return wrap
def remove_spec(spec):
"""Remove Sdf.Spec authored opinion."""
if spec.expired:
return
if isinstance(spec, Sdf.PrimSpec):
# PrimSpec
parent = spec.nameParent
if parent:
view = parent.nameChildren
else:
# Assume PrimSpec is root prim
view = spec.layer.rootPrims
del view[spec.name]
elif isinstance(spec, Sdf.PropertySpec):
# Relationship and Attribute specs
del spec.owner.properties[spec.name]
elif isinstance(spec, Sdf.VariantSetSpec):
# Owner is Sdf.PrimSpec (or can also be Sdf.VariantSpec)
del spec.owner.variantSets[spec.name]
elif isinstance(spec, Sdf.VariantSpec):
# Owner is Sdf.VariantSetSpec
spec.owner.RemoveVariant(spec)
else:
raise TypeError(f"Unsupported spec type: {spec}")
class LookdevOnlyExportChaser(mayaUsdLib.ExportChaser):
"""Keep only lookdev related data in the USD file.
Keep only:
- Materials + Shaders (and their children)
- Boundable Prim attributes 'material:binding' and 'primvars:arnold:'
- For GeomSubset that has material binding, include the the full GeomSubset
"""
def __init__(self, factoryContext, *args, **kwargs):
super(LookdevOnlyExportChaser, self).__init__(factoryContext, *args,
**kwargs)
self.log = logging.getLogger("lookdev_chaser")
self.dag_to_usd = factoryContext.GetDagToUsdMap()
self.stage = factoryContext.GetStage()
self.job_args = factoryContext.GetJobArgs()
@log_errors
def PostExport(self):
# TODO: We might want to clear "kind" or "ApiSchemas" on a PrimSpec
# Strip all geometry attributes like points, uvs, etc.
# but keep all renderer related attributes
# Exclude `texCoord2f[]` attributes, e.g. `primvars:st`
from pxr import UsdGeom, UsdShade, Usd
include_paths = set()
boundables = set()
# Find paths to include in the output
iterator = iter(self.stage.Traverse())
for prim in iterator:
prim_path = prim.GetPath()
# Include shaders/materials completely
if prim.IsA(UsdShade.NodeGraph) or prim.IsA(UsdShade.Shader):
# Include self and all children always
include_paths.add(prim_path)
iterator.PruneChildren()
continue
# For boundable prims get any arnold vars or material bindings
is_boundable = prim.IsA(UsdGeom.Boundable)
for property in prim.GetProperties():
if not property.IsAuthored():
continue
name = property.GetName()
property_path = prim_path.AppendProperty(name)
if is_boundable and "primvars:arnold" in name:
include_paths.add(property_path)
continue
elif "material:binding" in name:
# For GeomSubset include the full prim
if prim.IsA(UsdGeom.Subset):
# Include self and all children always
include_paths.add(prim_path)
iterator.PruneChildren()
break
else:
include_paths.add(property_path)
continue
for layer in self.stage.GetLayerStack():
specs_to_remove = []
def find_specs_to_remove(path):
if path.IsAbsoluteRootPath():
return
# include paths that are explicitly included
if path in include_paths:
return
# include property if the prim is included
if path.GetPrimPath() in include_paths:
return
# always include children paths
for parent_path in path.GetAncestorsRange():
if parent_path in include_paths:
return
specs_to_remove.append(path)
layer.Traverse("/", find_specs_to_remove)
# Iterate in reverse so we iterate the highest paths
# first, so when removing a spec the children specs
# are already removed
for spec_path in reversed(specs_to_remove):
spec = layer.GetObjectAtPath(spec_path)
if not spec or spec.expired:
continue
if isinstance(spec, Sdf.PrimSpec) and any(path.HasPrefix(spec_path) for path in include_paths):
# If any children are still to be included
# then we should not remove the spec completely
# but strip it, keeping only the children
self.log.debug("Override: %s", spec_path)
spec.specifier = Sdf.SpecifierOver
else:
self.log.debug("Removing: %s | %s", spec.typeName, spec_path)
remove_spec(spec)
return True
# Testing
from maya import cmds
cmds.loadPlugin("mayaUsdPlugin", quiet=True)
# Unregister en register to update the registry so we can run
# this code multiple times and always use the latest state
mayaUsdLib.ExportChaser.Unregister(LookdevOnlyExportChaser, "lookdev")
mayaUsdLib.ExportChaser.Register(LookdevOnlyExportChaser, "lookdev")
# We want to export a Maya USD file with `jobContext` set to `Arnold`
# but additionally include our own `chaser`. Unfortunately you can't
# seem to mix `jobContext` with `chaser` because the `chaser` will
# be completely ignored. So we convert `jobContext` to its individual
# arguments for the export so we can append our own chaser
def parse_arguments(text):
data = {}
for key_value in text.split(";"):
key_value = key_value.strip()
if not key_value:
continue
key, value = key_value.split("=", 1)
if value.startswith("[") and value.endswith("]"):
value = value[1:-1]
value = value.split(",")
data[key] = value
return data
arguments = cmds.mayaUSDListJobContexts(exportArguments="Arnold")
kwargs = parse_arguments(arguments)
kwargs.setdefault("chaser", []).append("lookdev")
# Perform the export (preserve selection, just for ease of testing)
sel = cmds.ls(selection=True)
try:
cmds.mayaUSDExport(file="C:/Users/User/Desktop/render.usda",
# frameRange=(1, 5),
# frameStride=1.0,
# mergeTransformAndShape=True,
selection=True,
**kwargs)
finally:
cmds.select(sel, replace=True, noExpand=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment