diff --git a/lib/mayaUsd/python/__init__.py b/lib/mayaUsd/python/__init__.py index 5ecd656209..b3fb7045c5 100644 --- a/lib/mayaUsd/python/__init__.py +++ b/lib/mayaUsd/python/__init__.py @@ -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 diff --git a/lib/mayaUsd/ufe/Global.cpp b/lib/mayaUsd/ufe/Global.cpp index da15400cab..46216aea86 100644 --- a/lib/mayaUsd/ufe/Global.cpp +++ b/lib/mayaUsd/ufe/Global.cpp @@ -431,6 +431,8 @@ MStatus finalize(bool exiting) g_MayaUIInfoHandler.reset(); #endif + UsdUfe::clearAllEditRouters(); + MMessage::removeCallback(gExitingCbId); return MS::kSuccess; diff --git a/lib/usdUfe/python/wrapEditRouter.cpp b/lib/usdUfe/python/wrapEditRouter.cpp index 6e84f7934d..5581b46c9c 100644 --- a/lib/usdUfe/python/wrapEditRouter.cpp +++ b/lib/usdUfe/python/wrapEditRouter.cpp @@ -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 { @@ -182,6 +189,8 @@ void wrapEditRouter() def("restoreAllDefaultEditRouters", &UsdUfe::restoreAllDefaultEditRouters); + def("clearAllEditRouters", &UsdUfe::clearAllEditRouters); + using OpThis = UsdUfe::OperationEditRouterContext; class_("OperationEditRouterContext", no_init) .def("__init__", make_constructor(OperationEditRouterContextInit)); diff --git a/lib/usdUfe/utils/editRouter.cpp b/lib/usdUfe/utils/editRouter.cpp index e50a0d4467..90d364e70f 100644 --- a/lib/usdUfe/utils/editRouter.cpp +++ b/lib/usdUfe/utils/editRouter.cpp @@ -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) { diff --git a/lib/usdUfe/utils/editRouter.h b/lib/usdUfe/utils/editRouter.h index 628f87d7ed..de376741e6 100644 --- a/lib/usdUfe/utils/editRouter.h +++ b/lib/usdUfe/utils/editRouter.h @@ -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(); diff --git a/test/lib/ufe/testEditRouting.py b/test/lib/ufe/testEditRouting.py index d024b0afad..4e842324dd 100644 --- a/test/lib/ufe/testEditRouting.py +++ b/test/lib/ufe/testEditRouting.py @@ -131,6 +131,7 @@ def setUp(self): def tearDown(self): # Restore default edit routers. + mayaUsd.lib.clearAllEditRouters() mayaUsd.lib.restoreAllDefaultEditRouters() def _prepareSimpleScene(self): @@ -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)