-
Notifications
You must be signed in to change notification settings - Fork 214
Description
Describe the bug
Conventional examples of usd rigging workflows use assets the Usd description of the Maya Reference and the static/cached representation the asset in the composed stage are mutually exclusive variants in a given set.
I am currently prototyping a method to load a rig reference on-demand when using "editAsMaya" on the asset root (character_01) that does not require variant swaps or use of the Sdf API.
In this example the rigs to load are defined under a usd class:
#usda 1.0
(
)
def Scope "Assets" (
kind = "assembly"
) {
def Scope "characters" (
kind = "group"
) {
def Xform "character_01" (
kind = "assembly"
) {
def Xform "body" (
kind = "component"
) {
}
def Xform "eyes" (
kind = "component"
) {
}
def Xform "hair" (
kind = "component"
) {
}
class "rig" (
) {
def Scope "anim_rig" () {
def MayaReference "main" () {
asset mayaReference = @./my_rig.ma@
string mayaNamespace = "my_rig"
}
}
}
}
}
}
import mayaUsd.lib
from maya import cmds
from maya.api import OpenMaya
def resolve_rig_path(prim):
maya_reference = prim.GetAttribute('mayaReference')
rig_path = maya_reference.Get().resolvedPath if maya_reference else ""
return rig_path
def get_rig_prims(prim):
# hardcoded example
rig_prim = prim.GetPrimAtPath('rig/anim_rig/main')
return [rig_prim] if rig_prim.IsValid() else []
class RigPrimReader(mayaUsd.lib.PrimReader):
@classmethod
def CanImport(cls, args, prim):
return mayaUsdLib.PrimReader.ContextSupport.Supported
def Read(self, context):
prim = self._GetArgs().GetUsdPrim()
print(f"[INFO] processing: {prim}")
if context.GetPruneChildren():
print(f'[INFO] processing children: {context.GetPruneChildren()}')
return False
rig_prims = get_rig_prims(prim)
if not rig_prims:
print(f"[INFO] skipping: {prim}")
return False
rig_network = cmds.createNode('transform', name=prim.GetName())
for rig_prim in rig_prims:
print(f"[INFO] Processing rig: {rig_prim}")
rig_parent = cmds.createNode('transform', name=rig_prim.GetName())
maya_file_path = resolve_rig_path(rig_prim)
ns_attr = rig_prim.GetAttribute("mayaNamespace")
namespace = ns_attr.Get() if ns_attr and ns_attr.HasAuthoredValueOpinion() else rig_prim.GetName()
print(f"[INFO] Loading rig: {maya_file_path} (ns={namespace})")
try:
ref_node = cmds.file(
maya_file_path,
reference=True,
namespace=namespace,
ignoreVersion=True,
mergeNamespacesOnClash=False,
options="v=0",
returnNewNodes=True
)
except RuntimeError as err:
print("[WARN] Maya RuntimeError suppressed:", err)
except Exception as e:
print("[WARN] Other error suppressed:", e)
finally:
print("[INFO] Rig reference attempted; continuing pipeline.")
print("+_+_+_+_+_+_+_+_+_+_+_")
print("[INFO] ref_node:", ref_node)
all_nodes = cmds.ls(ref_node, type="transform")
rig_top_nodes = [n for n in all_nodes if not cmds.listRelatives(n, parent=True)]
print("[INFO] ref_node:", rig_top_nodes)
if rig_top_nodes:
cmds.parent(rig_parent, rig_network)
cmds.parent(rig_top_nodes[0], rig_parent)
print(f'Read {prim.GetName()} - {rig_prim.GetName()} created')
# parent under __mayaUsd__
parent = context.GetMayaNode(prim.GetPath().GetParentPath(), True)
parentDagPath = OpenMaya.MFnDagNode(parent).fullPathName()
print("[INFO] parentDagPath:", rig_network, parentDagPath)
print("[INFO] DAG PATH Exist: ", cmds.objExists(parentDagPath))
cmds.parent(rig_network, parentDagPath)
selectionList = OpenMaya.MSelectionList()
selectionList.add(rig_network)
new_obj = selectionList.getDependNode(0)
context.RegisterNewMayaNode(prim.GetPath().pathString, new_obj)
# do not read children of character_01
context.SetPruneChildren(True)
print(f'[INFO] stopping at prim {prim}: {context.GetPruneChildren()}')
return True
class RigPrimUpdater(mayaUsd.lib.PrimUpdater):
@classmethod
def sRegister(cls):
print(f'Registering {cls.__qualname__}')
mayaUsd.lib.PrimUpdater.Register(
cls, "CustomRig", "transform", RigPrimUpdater.Supports.All.value
)
return True
@classmethod
def sUnregister(cls):
print(f'Un-registering {cls.__qualname__}')
mayaUsd.lib.PrimUpdater.Unregister(
cls, "CustomRig", "transform", RigPrimUpdater.Supports.All.value
)
return True
def canEditAsMaya(self):
print('canEditAsMaya', self.__class__.__name__)
return True
def editAsMaya(self):
print('[INFO] editAsMaya', self.__class__.__name__)
return super(RigPrimUpdater, self).editAsMaya()
def register():
RigPrimUpdater.sRegister()
mayaUsd.lib.PrimReader.Register(RigPrimReader, "CustomRig")
register()
def test_edit_as_maya(stage_dag_path, prim_path):
root_prim = mayaUsd.lib.GetPrim(stage_dag_path) # DAG path only
stage = root_prim.GetStage()
root_prim.GetPrimAtPath(prim_path).SetTypeName('CustomRig')
ufepath = f'{stage_dag_path},{prim_path}'
with mayaUsd.lib.OpUndoItemList():
mayaUsd.lib.PrimUpdaterManager.editAsMaya(ufepath)
# read character_01 via RigPrimReader
test_edit_as_maya('|shot|shotShape', '/Assets/characters/character_01')Steps to reproduce
Steps to reproduce the behavior:
- Create > USD > Stage From File...
- pick
shot.usdafromshot_and_rig.zip - Run the code above
Expected behavior
The main rig is loaded under character_01 as expected.
The Maya transforms for /Assets/characters/character_01/body, eyes and hair should not be created, but they are (see screenshot).
Attachments
Specs (if applicable):
- OS & version: macOS 15.4.1
- Compiler & version Pre-Built version of the plug-in
- Maya version Maya 2026.2
- Maya USD commit SHA 0.33.0 (0.33.0_202507022244-1cbb7f7)
- Pixar USD commit SHA 0.24.11
Additional context
The idea is to ensure that the geometric description of the asset is still accessible on the stage via usd APIs and edits don't have to be routed through composition arcs like variants or prim references.
Looking at the code for materialReader and testCustomRig.py it looked possible in principle.