diff --git a/lib/mayaUsd/commands/Readme.md b/lib/mayaUsd/commands/Readme.md index 96d33a1ce4..acb347721c 100644 --- a/lib/mayaUsd/commands/Readme.md +++ b/lib/mayaUsd/commands/Readme.md @@ -87,18 +87,27 @@ pipeline-specific operations and/or early prototyping of features that might otherwise not make sense to be part of the mainline codebase. Chasers are registered with a particular name and can be passed argument -name/value pairs in an invocation of `mayaUSDImport`. There is no "plugin -discovery" method here – the developer/user is responsible for making sure the -chaser is registered via a call to the convenience macro -`USDMAYA_DEFINE_IMPORT_CHASER_FACTORY(name, ctx)`, where `name` is the name of -the chaser being created. Unlike export chasers, import chasers also have the -ability to define `Undo` and `Redo` methods in order to allow the +name/value pairs in an invocation of `mayaUSDImport`. The chaser is registered +via a call to the convenience macro `USDMAYA_DEFINE_IMPORT_CHASER_FACTORY(name, +ctx)`, where `name` is the name of the chaser being created. Import chaser +plugins can be discovered and loaded automatically by declaring the +`UsdMaya:ImportChaserPlugin` metadata in their `plugInfo.json`: + +```json +"Info": { + "UsdMaya": { + "ImportChaserPlugin": {} + } +} +``` + +Unlike export chasers, import chasers also have the ability to define `Undo` and +`Redo` methods in order to allow the `mayaUSDImport` command to remain compliant with the Maya undo stack. It's not necessary to compile your chaser plugin together with `mayaUsdPlugin` in order to work; you can create a completely separate maya DLL that contains the business logic of your chaser code, and just call the aforementioned -`USDMAYA_DEFINE_IMPORT_CHASER_FACTORY` to register it, as long as the -`mayaUsdPlugin` DLL is loaded first. +`USDMAYA_DEFINE_IMPORT_CHASER_FACTORY` to register it. A sample import chaser, `infoImportChaser.cpp`, is provided to give an example of how to write an import chaser. All it does is read any custom layer data in @@ -399,9 +408,17 @@ implement prim post-processing that executes immediately after prims are written (and/or after animation is written to a prim in time-based exports). Chasers are registered with a particular name and can be passed argument name/value pairs in an invocation of a concrete -`MayaUSDExportCommand` command. There is no "plugin discovery" method -here – the developer/user is responsible for making sure the chaser is -registered. +`MayaUSDExportCommand` command. Export chaser plugins can be discovered +and loaded automatically by declaring the `UsdMaya:ExportChaserPlugin` +metadata in their `plugInfo.json`: + +```json +"Info": { + "UsdMaya": { + "ExportChaserPlugin": {} + } +} +``` For example the pxr plug-in provides one such chaser plugin called `AlembicChaser` to try to make integrating USD into Alembic-heavy diff --git a/lib/mayaUsd/fileio/chaser/exportChaserRegistry.cpp b/lib/mayaUsd/fileio/chaser/exportChaserRegistry.cpp index 030e31eed5..b696c746f8 100644 --- a/lib/mayaUsd/fileio/chaser/exportChaserRegistry.cpp +++ b/lib/mayaUsd/fileio/chaser/exportChaserRegistry.cpp @@ -67,7 +67,9 @@ bool UsdMayaExportChaserRegistry::RegisterFactory( UsdMayaExportChaserRefPtr UsdMayaExportChaserRegistry::Create(const std::string& name, const FactoryContext& context) const { + UsdMaya_RegistryHelper::LoadExportChaserPlugins(); TfRegistryManager::GetInstance().SubscribeTo(); + if (UsdMayaExportChaserRegistry::FactoryFn fn = _factoryRegistry[name]) { return TfCreateRefPtr(fn(context)); } else { diff --git a/lib/mayaUsd/fileio/chaser/importChaserRegistry.cpp b/lib/mayaUsd/fileio/chaser/importChaserRegistry.cpp index 43547dd5e7..fc7792a8ec 100644 --- a/lib/mayaUsd/fileio/chaser/importChaserRegistry.cpp +++ b/lib/mayaUsd/fileio/chaser/importChaserRegistry.cpp @@ -80,7 +80,9 @@ bool UsdMayaImportChaserRegistry::RegisterFactory(const char* name, FactoryFn fn UsdMayaImportChaserRefPtr UsdMayaImportChaserRegistry::Create(const char* name, const FactoryContext& context) const { + UsdMaya_RegistryHelper::LoadImportChaserPlugins(); TfRegistryManager::GetInstance().SubscribeTo(); + if (UsdMayaImportChaserRegistry::FactoryFn fn = _factoryImportRegistry[name]) { return TfCreateRefPtr(fn(context)); } else { diff --git a/lib/mayaUsd/fileio/registryHelper.cpp b/lib/mayaUsd/fileio/registryHelper.cpp index 4220037940..dc2632677b 100644 --- a/lib/mayaUsd/fileio/registryHelper.cpp +++ b/lib/mayaUsd/fileio/registryHelper.cpp @@ -42,6 +42,8 @@ TF_DEFINE_PRIVATE_TOKENS( (UsdMaya) (ShadingModePlugin) (JobContextPlugin) + (ImportChaserPlugin) + (ExportChaserPlugin) ); // clang-format on @@ -140,6 +142,42 @@ static bool _HasMayaPlugin( return true; } +static void _FindAndLoadUsdMayaPlugins(const TfToken& pluginKey) +{ + const std::vector scope { _tokens->UsdMaya, pluginKey }; + + for (const auto& plug : PlugRegistry::GetInstance().GetAllPlugins()) { + std::string mayaPlugin; + if (_HasMayaPlugin(plug, scope, &mayaPlugin)) { + if (!mayaPlugin.empty()) { + TF_DEBUG(PXRUSDMAYA_REGISTRY) + .Msg( + "Found %s %s: Loading via Maya API %s.\n", + pluginKey.GetText(), + plug->GetName().c_str(), + mayaPlugin.c_str()); + std::string loadPluginCmd + = TfStringPrintf("loadPlugin -quiet %s", mayaPlugin.c_str()); + if (MGlobal::executeCommand(loadPluginCmd.c_str())) { + // Need to ensure Python script modules are loaded + // properly for this library (Maya's loadPlugin will not + // load script modules like TfDlopen would). + TfScriptModuleLoader::GetInstance().LoadModules(); + } else { + TF_CODING_ERROR("Unable to load mayaplugin %s\n", mayaPlugin.c_str()); + } + } else { + TF_DEBUG(PXRUSDMAYA_REGISTRY) + .Msg( + "Found %s %s: Loading via USD API.\n", + pluginKey.GetText(), + plug->GetName().c_str()); + plug->Load(); + } + } + } +} + /* static */ std::string _PluginDictScopeToDebugString(const std::vector& scope) { @@ -202,81 +240,29 @@ void UsdMaya_RegistryHelper::FindAndLoadMayaPlug( /* static */ void UsdMaya_RegistryHelper::LoadShadingModePlugins() { - static std::once_flag _shadingModesLoaded; - static std::vector scope = { _tokens->UsdMaya, _tokens->ShadingModePlugin }; - std::call_once(_shadingModesLoaded, []() { - PlugPluginPtrVector plugins = PlugRegistry::GetInstance().GetAllPlugins(); - std::string mayaPlugin; - TF_FOR_ALL(plugIter, plugins) - { - PlugPluginPtr plug = *plugIter; - if (_HasMayaPlugin(plug, scope, &mayaPlugin)) { - if (!mayaPlugin.empty()) { - TF_DEBUG(PXRUSDMAYA_REGISTRY) - .Msg( - "Found shading mode plugin %s: Loading via Maya API %s.\n", - plug->GetName().c_str(), - mayaPlugin.c_str()); - std::string loadPluginCmd - = TfStringPrintf("loadPlugin -quiet %s", mayaPlugin.c_str()); - if (MGlobal::executeCommand(loadPluginCmd.c_str())) { - // Need to ensure Python script modules are loaded - // properly for this library (Maya's loadPlugin will not - // load script modules like TfDlopen would). - TfScriptModuleLoader::GetInstance().LoadModules(); - } else { - TF_CODING_ERROR("Unable to load mayaplugin %s\n", mayaPlugin.c_str()); - } - } else { - TF_DEBUG(PXRUSDMAYA_REGISTRY) - .Msg( - "Found shading mode plugin %s: Loading via USD API.\n", - plug->GetName().c_str()); - plug->Load(); - } - } - } - }); + static std::once_flag _loaded; + std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->ShadingModePlugin); }); } /* static */ void UsdMaya_RegistryHelper::LoadJobContextPlugins() { - static std::once_flag _jobContextsLoaded; - static std::vector scope = { _tokens->UsdMaya, _tokens->JobContextPlugin }; - std::call_once(_jobContextsLoaded, []() { - PlugPluginPtrVector plugins = PlugRegistry::GetInstance().GetAllPlugins(); - std::string mayaPlugin; - TF_FOR_ALL(plugIter, plugins) - { - PlugPluginPtr plug = *plugIter; - if (_HasMayaPlugin(plug, scope, &mayaPlugin)) { - if (!mayaPlugin.empty()) { - TF_DEBUG(PXRUSDMAYA_REGISTRY) - .Msg( - "Found job context plugin %s: Loading via Maya API %s.\n", - plug->GetName().c_str(), - mayaPlugin.c_str()); - std::string loadPluginCmd - = TfStringPrintf("loadPlugin -quiet %s", mayaPlugin.c_str()); - if (MGlobal::executeCommand(loadPluginCmd.c_str())) { - // Need to ensure Python script modules are loaded - // properly for this library (Maya's loadPlugin will not - // load script modules like TfDlopen would). - TfScriptModuleLoader::GetInstance().LoadModules(); - } else { - TF_CODING_ERROR("Unable to load mayaplugin %s\n", mayaPlugin.c_str()); - } - } else { - TF_DEBUG(PXRUSDMAYA_REGISTRY) - .Msg( - "Found job context plugin %s: Loading via USD API.\n", - plug->GetName().c_str()); - plug->Load(); - } - } - } - }); + static std::once_flag _loaded; + std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->JobContextPlugin); }); +} + +/* static */ +void UsdMaya_RegistryHelper::LoadImportChaserPlugins() +{ + static std::once_flag _loaded; + std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->ImportChaserPlugin); }); +} + +/* static */ +void UsdMaya_RegistryHelper::LoadExportChaserPlugins() +{ + static std::once_flag _loaded; + std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->ExportChaserPlugin); }); } /* static */ diff --git a/lib/mayaUsd/fileio/registryHelper.h b/lib/mayaUsd/fileio/registryHelper.h index bf18d087c1..f80e47ec1c 100644 --- a/lib/mayaUsd/fileio/registryHelper.h +++ b/lib/mayaUsd/fileio/registryHelper.h @@ -74,6 +74,30 @@ struct UsdMaya_RegistryHelper /// usdMaya will try to load the "mayaPlugin" when job contexts are first accessed. static void LoadJobContextPlugins(); + /// Searches the plugInfos and looks for ImportChaserPlugin. + /// + /// "UsdMaya" : { + /// "ImportChaserPlugin" : { + /// "mayaPlugin" : "myImportChaserPlugin" + /// } + /// } + /// + /// At that scope, it expects an optional "mayaPlugin" key. + /// usdMaya will try to load the plugin when import chasers are first accessed. + static void LoadImportChaserPlugins(); + + /// Searches the plugInfos and looks for ExportChaserPlugin. + /// + /// "UsdMaya" : { + /// "ExportChaserPlugin" : { + /// "mayaPlugin" : "myExportChaserPlugin" + /// } + /// } + /// + /// At that scope, it expects an optional "mayaPlugin" key. + /// usdMaya will try to load the plugin when export chasers are first accessed. + static void LoadExportChaserPlugins(); + /// Searches the plugInfos for metadata dictionaries at the given \p scope, /// and composes them together. /// The scope are the nested keys to search through in the plugInfo (for diff --git a/test/lib/usd/plugin/CMakeLists.txt b/test/lib/usd/plugin/CMakeLists.txt index 8665a74c01..62142e2569 100644 --- a/test/lib/usd/plugin/CMakeLists.txt +++ b/test/lib/usd/plugin/CMakeLists.txt @@ -181,6 +181,12 @@ if(IS_MACOSX OR IS_LINUX) mayaUsd_install_rpath(rpath ${TARGET_NAME}) endif() +# ----------------------------------------------------------------------------- +# Plug Plug-in file +# ----------------------------------------------------------------------------- +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/infoImportChaser_plugInfo.json" + "${CMAKE_CURRENT_BINARY_DIR}/infoImportChaser/plugInfo.json" +) # ----------------------------------------------------------------------------- # Plug Plug-ins diff --git a/test/lib/usd/plugin/infoImportChaser_plugInfo.json b/test/lib/usd/plugin/infoImportChaser_plugInfo.json new file mode 100644 index 0000000000..ed9bae7c7c --- /dev/null +++ b/test/lib/usd/plugin/infoImportChaser_plugInfo.json @@ -0,0 +1,16 @@ +{ + "Plugins": [ + { + "Info": { + "UsdMaya": { + "ImportChaserPlugin": { + "mayaPlugin": "usdTestInfoImportChaser" + } + } + }, + "Name": "usdTestInfoImportChaser", + "LibraryPath": "@CMAKE_CURRENT_BINARY_DIR@/@CMAKE_SHARED_LIBRARY_PREFIX@usdTestInfoImportChaser@CMAKE_SHARED_LIBRARY_SUFFIX@", + "Type": "library" + } + ] +} \ No newline at end of file diff --git a/test/lib/usd/plugin/nullApiExporter_plugInfo.json b/test/lib/usd/plugin/nullApiExporter_plugInfo.json index a50e59279d..9606a08731 100644 --- a/test/lib/usd/plugin/nullApiExporter_plugInfo.json +++ b/test/lib/usd/plugin/nullApiExporter_plugInfo.json @@ -4,6 +4,7 @@ "Info": { "UsdMaya": { "JobContextPlugin": {}, + "ExportChaserPlugin": {}, "SchemaApiAdaptor": { "providesTranslator": [ "shape" diff --git a/test/lib/usd/translators/CMakeLists.txt b/test/lib/usd/translators/CMakeLists.txt index 97cbf1125a..746a609a78 100644 --- a/test/lib/usd/translators/CMakeLists.txt +++ b/test/lib/usd/translators/CMakeLists.txt @@ -232,7 +232,7 @@ mayaUsd_add_test(testUsdImportChaser WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ENV "MAYA_PLUG_IN_PATH=${CMAKE_CURRENT_BINARY_DIR}/../plugin" - "${PXR_OVERRIDE_PLUGINPATH_NAME}=${CMAKE_CURRENT_BINARY_DIR}/../plugin" + "${PXR_OVERRIDE_PLUGINPATH_NAME}=${CMAKE_CURRENT_BINARY_DIR}/../plugin/infoImportChaser" "LD_LIBRARY_PATH=${ADDITIONAL_LD_LIBRARY_PATH}" ) set_property(TEST testUsdImportChaser APPEND PROPERTY LABELS translators) diff --git a/test/lib/usd/translators/testUsdExportSchemaApi.py b/test/lib/usd/translators/testUsdExportSchemaApi.py index a28a355d88..b124d33778 100644 --- a/test/lib/usd/translators/testUsdExportSchemaApi.py +++ b/test/lib/usd/translators/testUsdExportSchemaApi.py @@ -23,7 +23,7 @@ import maya.api.OpenMaya as om import maya.api.OpenMayaAnim as oma -from pxr import Tf, Gf, Usd +from pxr import Plug, Tf, Gf, Usd import fixturesUtils @@ -137,6 +137,24 @@ def testExportJobContextConflicts(self): cmds.file(f=True, new=True) + def testExportChaserPluginLoading(self): + """Testing that export chaser plugins can be automatically loaded by mayaUSDExport from plugInfo.""" + + chaserPlugin = Plug.Registry().GetPluginWithName("usdTestApiWriter") + + # Ensure the plugin is not loaded before the test + self.assertIsNotNone(chaserPlugin) + self.assertFalse(chaserPlugin.isLoaded) + + cmds.polySphere(r=1) + usdFilePath = os.path.abspath('UsdExportChaserPluginLoading.usda') + cmds.mayaUSDExport(file=usdFilePath, chaser=["NullAPIChaser"]) + + # Verify the plugin was loaded by the mayaUSDExport call. + self.assertTrue(chaserPlugin.isLoaded) + + cmds.file(f=True, new=True) + @unittest.skipUnless(HAS_BULLET and HAS_USDMAYA, 'Requires the Pixar and bullet plugins.') def testPluginSchemaAdaptors(self): """Testing a plugin Schema adaptor in a generic context:""" diff --git a/test/lib/usd/translators/testUsdImportChaser.py b/test/lib/usd/translators/testUsdImportChaser.py index e2e7c64ad0..1abc9a04c8 100644 --- a/test/lib/usd/translators/testUsdImportChaser.py +++ b/test/lib/usd/translators/testUsdImportChaser.py @@ -53,8 +53,14 @@ def tearDownClass(cls): mayastandalone.uninitialize() def testImportChaser(self): - cmds.loadPlugin('usdTestInfoImportChaser') # NOTE: (yliangsiew) We load this plugin since the chaser is compiled as part of it. + # ensure the plugin is not loaded, e.g. by another test + self.assertFalse(cmds.pluginInfo('usdTestInfoImportChaser', q=True, loaded=True)) + rootPaths = cmds.mayaUSDImport(v=True, f=self.stagePath, chaser=['info']) + + # verify that the plugin was discovered and loaded + self.assertTrue(cmds.pluginInfo('usdTestInfoImportChaser', q=True, loaded=True)) + self.assertEqual(len(rootPaths), 1) sl = om.MSelectionList() sl.add(rootPaths[0])