Skip to content

Commit

Permalink
EMSUSD-1361 Same-name Python edit routers
Browse files Browse the repository at this point in the history
If two Python edit routers were written in the Maya script editor using
the same function name, then the first one would no longer be held in
memory and would crash when called. Fix this by increasing the Python
reference count when registering and decreasing it when unregistering.

Make the cleanup of registered Python routers safer during exit.
Make the clearAllEditRouters function accessible from Python.

Add a unit test to cover this problem.
  • Loading branch information
pierrebai-adsk committed Jul 17, 2024
1 parent ed4df92 commit 8f46e51
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 2 deletions.
1 change: 1 addition & 0 deletions lib/mayaUsd/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from usdUfe import registerStageLayerEditRouter
from usdUfe import restoreDefaultEditRouter
from usdUfe import restoreAllDefaultEditRouters
from usdUfe import clearAllEditRouters
from usdUfe import OperationEditRouterContext
from usdUfe import AttributeEditRouterContext
from usdUfe import registerUICallback
Expand Down
2 changes: 2 additions & 0 deletions lib/mayaUsd/ufe/Global.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ MStatus finalize(bool exiting)
g_MayaUIInfoHandler.reset();
#endif

UsdUfe::clearAllEditRouters();

MMessage::removeCallback(gExitingCbId);

return MS::kSuccess;
Expand Down
11 changes: 10 additions & 1 deletion lib/usdUfe/python/wrapEditRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,16 @@ class PyEditRouter : public UsdUfe::EditRouter
PyEditRouter(PyObject* pyCallable)
: _pyCb(pyCallable)
{
if (_pyCb)
Py_INCREF(_pyCb);
}

~PyEditRouter() override { }
~PyEditRouter() override
{
PXR_NS::TfPyLock pyLock;
if (_pyCb)
Py_DECREF(_pyCb);
}

void operator()(const PXR_NS::VtDictionary& context, PXR_NS::VtDictionary& routingData) override
{
Expand Down Expand Up @@ -182,6 +189,8 @@ void wrapEditRouter()

def("restoreAllDefaultEditRouters", &UsdUfe::restoreAllDefaultEditRouters);

def("clearAllEditRouters", &UsdUfe::clearAllEditRouters);

using OpThis = UsdUfe::OperationEditRouterContext;
class_<OpThis, boost::noncopyable>("OperationEditRouterContext", no_init)
.def("__init__", make_constructor(OperationEditRouterContextInit));
Expand Down
4 changes: 3 additions & 1 deletion lib/usdUfe/utils/editRouter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,11 @@ bool restoreDefaultEditRouter(const PXR_NS::TfToken& operation)
return true;
}

void clearAllEditRouters() { getRegisteredEditRouters().clear(); }

void restoreAllDefaultEditRouters()
{
getRegisteredEditRouters().clear();
clearAllEditRouters();

auto defaults = defaultEditRouters();
for (const auto& entry : defaults) {
Expand Down
6 changes: 6 additions & 0 deletions lib/usdUfe/utils/editRouter.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,12 @@ void restoreAllDefaultEditRouters();
USDUFE_PUBLIC
void registerDefaultEditRouter(const PXR_NS::TfToken&, const EditRouter::Ptr&);

// Clear all registered edit routers.
// Mostly only used on exit to ensure edit routers are cleared to avoid order of destruction
// problems.
USDUFE_PUBLIC
void clearAllEditRouters();

// Return built-in default edit routers.
EditRouters defaultEditRouters();

Expand Down
32 changes: 32 additions & 0 deletions test/lib/ufe/testEditRouting.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def setUp(self):

def tearDown(self):
# Restore default edit routers.
mayaUsd.lib.clearAllEditRouters()
mayaUsd.lib.restoreAllDefaultEditRouters()

def _prepareSimpleScene(self):
Expand Down Expand Up @@ -687,7 +688,38 @@ def Xform "B"
'''
self.maxDiff = None
self.assertEqual(filterUsdStr(self.sessionLayer.ExportToString()), filterUsdStr(expectedContents))

def testRegisterLambdaEditRouter(self):
'''
Test registering lambda function and forgetiing bout it.
This test that the edit router system properly keeps a reference
on the given router.
'''
self._prepareSimpleScene()

# Regsiter a lambda as the edit router:

router = lambda context, routingData: routingData.update(
{'layer': context.get('prim').GetStage().GetSessionLayer().identifier})
mayaUsd.lib.registerEditRouter('visibility', router)
router = None

def setVisibility():
cmds.hide()

def verifyVisibility(sessionLayer):
self.assertIsNotNone(sessionLayer.GetPrimAtPath('/B'))

# Check that any visibility changes were written to the session layer
self.assertIsNotNone(sessionLayer.GetAttributeAtPath('/B.visibility').default)

# Check that correct visibility changes were written to the session layer
self.assertEqual(filterUsdStr(sessionLayer.ExportToString()),
filterUsdStr('over "B"\n{\n token visibility = "invisible"\n}'))

setVisibility()
verifyVisibility(self.sessionLayer)


if __name__ == '__main__':
unittest.main(verbosity=2)

0 comments on commit 8f46e51

Please sign in to comment.