Skip to content

Instantly share code, notes, and snippets.

@BigRoy
Last active November 13, 2024 23:22
Show Gist options
  • Save BigRoy/5dbe94ae66b33c206ef21b9a66411a8e to your computer and use it in GitHub Desktop.
Save BigRoy/5dbe94ae66b33c206ef21b9a66411a8e to your computer and use it in GitHub Desktop.
Maya USD Export Chaser to filter properties using SideFX-Houdini like parameter pattern matching
import re
import logging
from typing import List
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: Sdf.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}")
def remove_layer_specs(layer: Sdf.Layer, spec_paths: List[Sdf.Path]):
# 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(spec_paths):
spec = layer.GetObjectAtPath(spec_path)
if not spec or spec.expired:
continue
remove_spec(spec)
def match_pattern(name: str, text_pattern: str) -> bool:
"""SideFX Houdini like pattern matching"""
patterns = text_pattern.split(" ")
is_match = False
for pattern in patterns:
# * means any character
# ? means any single character
# [abc] means a, b, or c
pattern = pattern.strip(" ")
if not pattern:
continue
excludes = pattern[0] == "^"
# If name is already matched against earlier pattern in the text
# pattern, then we can skip the pattern if it is not an exclude pattern
if is_match and not excludes:
continue
if excludes:
pattern = pattern[1:]
pattern = re.escape(pattern)
pattern = pattern.replace(re.escape("*"), ".*")
pattern = pattern.replace(re.escape("?"), ".")
pattern = pattern.replace(re.escape("["), "[")
pattern = pattern.replace(re.escape("]"), "]")
pattern = f"^{pattern}$" # match full string
match = re.match(pattern, name)
if match:
is_match = not excludes
return is_match
class FilterPropertiesExportChaser(mayaUsdLib.ExportChaser):
"""Remove property specs based on pattern"""
name = "filterProperties"
def __init__(self, factoryContext, *args, **kwargs):
super().__init__(factoryContext, *args, **kwargs)
self.log = logging.getLogger(self.__class__.__name__)
self.stage = factoryContext.GetStage()
self.job_args = factoryContext.GetJobArgs()
@log_errors
def PostExport(self):
chaser_args = self.job_args.allChaserArgs[self.name]
# strip all or use user-specified pattern
pattern = chaser_args.get("pattern", "*")
for layer in self.stage.GetLayerStack():
specs_to_remove = []
def find_attribute_specs_to_remove(path: Sdf.Path):
if not path.IsPropertyPath():
return
spec = layer.GetObjectAtPath(path)
if not spec:
return
if not isinstance(spec, Sdf.PropertySpec):
return
if match_pattern(spec.name, pattern):
self.log.debug(f"Removing spec: %s", path)
specs_to_remove.append(path)
else:
self.log.debug(f"Keeping spec: %s", path)
layer.Traverse("/", find_attribute_specs_to_remove)
remove_layer_specs(layer, specs_to_remove)
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(FilterPropertiesExportChaser, "filterProperties")
mayaUsdLib.ExportChaser.Register(FilterPropertiesExportChaser, "filterProperties")
# 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=(1001, 1010),
# frameStride=1.0,
mergeTransformAndShape=True,
selection=True,
exportComponentTags=False,
chaser=["filterProperties"],
chaserArgs=[
# Filter all, but keep all xformOp* and points properties
("filterProperties", "pattern", "* ^xformOp* ^points")
])
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