Perform namespace edit inside a variant set edit target

I’d like to be able to apply an Sdf.NamespaceEdit inside a variant edit target, but it keeps crashing on me.

from pxr import Usd, Sdf

# Create a stage with a root xform
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/root", "Xform")

# Add variant set
variant_sets = prim.GetVariantSets()
variant_set = variant_sets.AddVariantSet("model")

# Add some variants
variant_set.AddVariant("main")
variant_set.AddVariant("damaged")
variant_set.AddVariant("ice")

# Set the variant selection
variant_set.SetVariantSelection("main")


edit_target = variant_set.GetVariantEditTarget()
stage.SetEditTarget(edit_target)

child = stage.DefinePrim("/root/child", "Xform")
spec = next(iter(child.GetPrimStack()))  # there is only one prim spec in this case, it's in the variant set
assert spec.path == '/root{model=main}child'

# Rename, keep parent
edit = Sdf.NamespaceEdit.Rename(
    spec.path,
    "new_name"
)

layer = spec.layer
batch_edit = Sdf.BatchNamespaceEdit()
batch_edit.Add(edit)
layer.Apply(batch_edit)

child = stage.GetPrimAtPath("/root/new_name")
assert child, "Renamed child must exist"
assert not stage.GetPrimAtPath("/root/child"), "Old prim name must not exist"

# However, now accessing the prim stack for the renamed prim crashes
prim_stack = child.GetPrimStack()  # CRASH

Am I missing something obvious? :slight_smile:

Running it in Maya provides me this crash log:

//=====================================================
Maya Crash Report
//=====================================================

Exception code: C0000005: ACCESS_VIOLATION - illegal read at address 0x00000000
Fault address:  D502A5FF in C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\usd_pcp.dll
0001:001895FF Logical offset (see .map file for location)

Call stack:
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\usd_usd.dll (-exported-)
	Location: usd_usd.dllpxrInternal_v0_22__pxrReserved__::UsdAttributeQuery::_Get<pxrInternal_v0_22__pxrReserved__::VtArray<pxrInternal_v0_22__pxrReserved__::GfVec4d> > + 79B164 bytes
	Decl: private: bool __cdecl pxrInternal_v0_22__pxrReserved__::UsdAttributeQuery::_Get<class pxrInternal_v0_22__pxrReserved__::VtArray<class pxrInternal_v0_22__pxrReserved__::GfVec4d> >(class pxrInternal_v0_22__pxrReserved__::VtArray<class pxrInternal_v0_22__px
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\usd_usd.dll (-exported-)
	Location: usd_usd.dllpxrInternal_v0_22__pxrReserved__::UsdAttributeQuery::_Get<pxrInternal_v0_22__pxrReserved__::VtArray<pxrInternal_v0_22__pxrReserved__::GfVec4d> > + 488E40 bytes
	Decl: private: bool __cdecl pxrInternal_v0_22__pxrReserved__::UsdAttributeQuery::_Get<class pxrInternal_v0_22__pxrReserved__::VtArray<class pxrInternal_v0_22__pxrReserved__::GfVec4d> >(class pxrInternal_v0_22__pxrReserved__::VtArray<class pxrInternal_v0_22__px
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\python\pxr\Usd\_usd.pyd (-exported-)
	Location: _usd.pydPyInit_libusd + 1910DA bytes
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::objects::function::call + 2D3 bytes
	Decl: public: struct _object * __ptr64 __cdecl boost::python::objects::function::call(struct _object * __ptr64,struct _object * __ptr64)const __ptr64
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::objects::static_data + 2AA bytes
	Decl: struct _object * __ptr64 __cdecl boost::python::objects::static_data(void)
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::detail::exception_handler::operator() + 3B bytes
	Decl: public: bool __cdecl boost::python::detail::exception_handler::operator()(class boost::function0<void> const & __ptr64)const __ptr64
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\python\pxr\Tf\_tf.pyd (-exported-)
	Location: _tf.pydpxrInternal_v0_22__pxrReserved__::tfTestStaticTokens_StaticTokenType::tfTestStaticTokens_StaticTokenType + 619F0 bytes
	Decl: public: __cdecl pxrInternal_v0_22__pxrReserved__::tfTestStaticTokens_StaticTokenType::tfTestStaticTokens_StaticTokenType(void) __ptr64
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::handle_exception_impl + 47 bytes
	Decl: bool __cdecl boost::python::handle_exception_impl(class boost::function0<void>)
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::objects::static_data + 35A bytes
	Decl: struct _object * __ptr64 __cdecl boost::python::objects::static_data(void)
	Module:  C:\Program Files\Autodesk\Maya2024\bin\python310.dll (-exported-)
	Location: python310.dll_PyObject_Call + 96 bytes
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\usd_tf.dll (-exported-)
	Location: usd_tf.dllpxrInternal_v0_22__pxrReserved__::TfPyEnsureGILUnlockedObj::TfPyEnsureGILUnlockedObj + 13415F bytes
	Decl: public: __cdecl pxrInternal_v0_22__pxrReserved__::TfPyEnsureGILUnlockedObj::TfPyEnsureGILUnlockedObj(void) __ptr64
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::objects::function::call + 2D3 bytes
	Decl: public: struct _object * __ptr64 __cdecl boost::python::objects::function::call(struct _object * __ptr64,struct _object * __ptr64)const __ptr64
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::objects::static_data + 2AA bytes
	Decl: struct _object * __ptr64 __cdecl boost::python::objects::static_data(void)
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::detail::exception_handler::operator() + 3B bytes
	Decl: public: bool __cdecl boost::python::detail::exception_handler::operator()(class boost::function0<void> const & __ptr64)const __ptr64
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\python\pxr\Tf\_tf.pyd (-exported-)
	Location: _tf.pydpxrInternal_v0_22__pxrReserved__::tfTestStaticTokens_StaticTokenType::tfTestStaticTokens_StaticTokenType + 619F0 bytes
	Decl: public: __cdecl pxrInternal_v0_22__pxrReserved__::tfTestStaticTokens_StaticTokenType::tfTestStaticTokens_StaticTokenType(void) __ptr64
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::handle_exception_impl + 47 bytes
	Decl: bool __cdecl boost::python::handle_exception_impl(class boost::function0<void>)
	Module:  C:\Program Files\Autodesk\MayaUSD\Maya2024\0.26.0_202311130904-e634cda\mayausd\USD\lib\boost_python310-vc142-mt-x64-1_76.dll (-exported-)
	Location: boost_python310-vc142-mt-x64-1_76.dllboost::python::objects::static_data + 35A bytes
	Decl: struct _object * __ptr64 __cdecl boost::python::objects::static_data(void)

It also crashes outside of Maya in Python with usd-core but I’m not sure how to get such a crash dump there.

It seems fine to access the PrimSpec through the Sdf.Layer instead of going through the Usd.Prim.GetPrimStack()

def debug(path):
    print("{:<30} {}".format(path.pathString, layer.GetPrimAtPath(path)))

layer.Traverse("/", debug)

Which prints:

/root{model=main}new_name      Sdf.Find('anon:000001E0BA91E820:tmp.usda', '/root{model=main}new_name')
/root{model=main}              Sdf.Find('anon:000001E0BA91E820:tmp.usda', '/root{model=main}')
/root{model=damaged}           Sdf.Find('anon:000001E0BA91E820:tmp.usda', '/root{model=damaged}')
/root{model=ice}               Sdf.Find('anon:000001E0BA91E820:tmp.usda', '/root{model=ice}')
/root{model=}                  None
/root                          Sdf.Find('anon:000001E0BA91E820:tmp.usda', '/root')
/                              Sdf.Find('anon:000001E0BA91E820:tmp.usda', '/')

Still - any app is likely to crash as soon as it starts touching the prim stacks, etc.

I wonder if this hints to anything, for the crashing prim when using prim.GetPrimStack() :warning: :

  • the Usd.Prim.GetPrimIndex() returns None
  • however it is considered valid Usd.Prim.IsValid() == True.
  • this also crashes: Usd.Prim.ComputeExpandedPrimIndex() :warning:

Would you mind filing this as an Issue on GitHub, @BigRoyNL ? This is timely as we are implementing namespace editing at the Usd stage/prim/property level, and it’ll be good to ensure test coverage of name-editing within a variant when that’s the selected EditTarget.

Created the issue here: Crash on namespace edit inside a variant edit target · Issue #2844 · PixarAnimationStudios/OpenUSD · GitHub

Would love to know if there’s any workaround to maybe somehow invalidate the Prim’s Index (PcpPrimIndex).

We will prioritize looking into this… the only thing I can suggest trying is, just before you do the Sdf-level name changing, deactivate </root> on the Stage - note it must be an ancestor of the prim you want to rename, and not the prim itself. This will cause the stage to completely forget about the source prim. After performing the edit, re-activate </root>, and hopefully the stage will re-index all of the child locations properly, and populate them on the stage. :crossed_fingers:

1 Like

Actually, that does work - even if you do it after the rename but before accessing the prim stack again.

Here are some examples that do not crash:

Deactivate/Reactivate parent after if `GetPrimIndex` is None (Click to expand)
from pxr import Usd, Sdf

# Create a stage with a root xform
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/root", "Xform")

# Add variant set
variant_sets = prim.GetVariantSets()
variant_set = variant_sets.AddVariantSet("model")

# Add some variants
variant_set.AddVariant("main")
variant_set.AddVariant("damaged")
variant_set.AddVariant("ice")

# Set the variant selection
variant_set.SetVariantSelection("main")


edit_target = variant_set.GetVariantEditTarget()
stage.SetEditTarget(edit_target)

child = stage.DefinePrim("/root/child", "Xform")
spec = next(iter(child.GetPrimStack()))  # there is only one prim spec in this case, it's in the variant set
assert spec.path == '/root{model=main}child'

# Rename, keep parent
edit = Sdf.NamespaceEdit.Rename(
    spec.path,
    "new_name"
)

layer = spec.layer
batch_edit = Sdf.BatchNamespaceEdit()
batch_edit.Add(edit)
layer.Apply(batch_edit)

# Avoid crash by deactivating + reactivating the parent
# to hopefully re-index the child locations and populate
# them on the stage
child = stage.GetPrimAtPath("/root/new_name")
if child.GetPrimIndex() is None:
    parent = child.GetParent()
    parent.SetActive(False)
    parent.SetActive(True)

child = stage.GetPrimAtPath("/root/new_name")
assert child, "Renamed child must exist"
assert not stage.GetPrimAtPath("/root/child"), "Old prim name must not exist"
prim_stack = child.GetPrimStack()
print(prim_stack)
Deactivate parent in context manager during `layer.Apply(batch_edit)` (Click to expand)
import contextlib
from pxr import Usd, Sdf

@contextlib.contextmanager
def deactivated_parent(prim: Usd.Prim):
    """Deactivate parent during context if it's currently active"""
    parent = prim.GetParent()
    was_active = parent.IsActive()
    if was_active:
        parent.SetActive(False)
    try:
        yield
    finally:
        if was_active:
            parent.SetActive(was_active)


# Create a stage with a root xform
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/root", "Xform")

# Add variant set
variant_sets = prim.GetVariantSets()
variant_set = variant_sets.AddVariantSet("model")

# Add some variants
variant_set.AddVariant("main")
variant_set.AddVariant("damaged")
variant_set.AddVariant("ice")

# Set the variant selection
variant_set.SetVariantSelection("main")


edit_target = variant_set.GetVariantEditTarget()
stage.SetEditTarget(edit_target)

child = stage.DefinePrim("/root/child", "Xform")
spec = next(iter(child.GetPrimStack()))  # there is only one prim spec in this case, it's in the variant set
assert spec.path == '/root{model=main}child'

# Rename, keep parent
edit = Sdf.NamespaceEdit.Rename(
    spec.path,
    "new_name"
)

layer = spec.layer
batch_edit = Sdf.BatchNamespaceEdit()
batch_edit.Add(edit)


with deactivated_parent(child):
    layer.Apply(batch_edit)


child = stage.GetPrimAtPath("/root/new_name")
assert child, "Renamed child must exist"
assert not stage.GetPrimAtPath("/root/child"), "Old prim name must not exist"
prim_stack = child.GetPrimStack()
print(prim_stack)
Set parent inactive/reactive for all new paths of batch edit edits` (Click to expand)
from pxr import Usd, Sdf


def fix_invalid_prim_indices_after_batch_edit(stage, batch_edit):
    for edit in batch_edit.edits:
        prim = stage.GetPrimAtPath(edit.newPath.StripAllVariantSelections())
        if prim and prim.GetPrimIndex() is None:
            parent = prim.GetParent()
            parent.SetActive(False)
            parent.SetActive(True)


# Create a stage with a root xform
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/root", "Xform")

# Add variant set
variant_sets = prim.GetVariantSets()
variant_set = variant_sets.AddVariantSet("model")

# Add some variants
variant_set.AddVariant("main")
variant_set.AddVariant("damaged")
variant_set.AddVariant("ice")

# Set the variant selection
variant_set.SetVariantSelection("main")


edit_target = variant_set.GetVariantEditTarget()
stage.SetEditTarget(edit_target)

child = stage.DefinePrim("/root/child", "Xform")
spec = next(iter(child.GetPrimStack()))  # there is only one prim spec in this case, it's in the variant set
assert spec.path == '/root{model=main}child'

# Rename, keep parent
edit = Sdf.NamespaceEdit.Rename(
    spec.path,
    "new_name"
)

layer = spec.layer
batch_edit = Sdf.BatchNamespaceEdit()
batch_edit.Add(edit)

layer.Apply(batch_edit)
fix_invalid_prim_indices_after_batch_edit(stage, batch_edit)

child = stage.GetPrimAtPath("/root/new_name")
assert child, "Renamed child must exist"
assert not stage.GetPrimAtPath("/root/child"), "Old prim name must not exist"
prim_stack = child.GetPrimStack()
print(prim_stack)

I’ve found the deactivate/reactivate afterwards directly to be more reliable somehow.