Skip to content

Commit 6c5eefe

Browse files
authored
Merge pull request #4642 from jufrantz/automatic_loading_of_chaser_plugins
Automatic loading of `ExportChaser` and `ImportChaser` plugins
2 parents e31ce5b + 29d980d commit 6c5eefe

11 files changed

Lines changed: 162 additions & 84 deletions

lib/mayaUsd/commands/Readme.md

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,27 @@ pipeline-specific operations and/or early prototyping of features that might
8787
otherwise not make sense to be part of the mainline codebase.
8888

8989
Chasers are registered with a particular name and can be passed argument
90-
name/value pairs in an invocation of `mayaUSDImport`. There is no "plugin
91-
discovery" method here – the developer/user is responsible for making sure the
92-
chaser is registered via a call to the convenience macro
93-
`USDMAYA_DEFINE_IMPORT_CHASER_FACTORY(name, ctx)`, where `name` is the name of
94-
the chaser being created. Unlike export chasers, import chasers also have the
95-
ability to define `Undo` and `Redo` methods in order to allow the
90+
name/value pairs in an invocation of `mayaUSDImport`. The chaser is registered
91+
via a call to the convenience macro `USDMAYA_DEFINE_IMPORT_CHASER_FACTORY(name,
92+
ctx)`, where `name` is the name of the chaser being created. Import chaser
93+
plugins can be discovered and loaded automatically by declaring the
94+
`UsdMaya:ImportChaserPlugin` metadata in their `plugInfo.json`:
95+
96+
```json
97+
"Info": {
98+
"UsdMaya": {
99+
"ImportChaserPlugin": {}
100+
}
101+
}
102+
```
103+
104+
Unlike export chasers, import chasers also have the ability to define `Undo` and
105+
`Redo` methods in order to allow the
96106
`mayaUSDImport` command to remain compliant with the Maya undo stack. It's not
97107
necessary to compile your chaser plugin together with `mayaUsdPlugin` in order
98108
to work; you can create a completely separate maya DLL that contains the
99109
business logic of your chaser code, and just call the aforementioned
100-
`USDMAYA_DEFINE_IMPORT_CHASER_FACTORY` to register it, as long as the
101-
`mayaUsdPlugin` DLL is loaded first.
110+
`USDMAYA_DEFINE_IMPORT_CHASER_FACTORY` to register it.
102111

103112
A sample import chaser, `infoImportChaser.cpp`, is provided to give an example
104113
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
399408
are written (and/or after animation is written to a prim in time-based
400409
exports). Chasers are registered with a particular name and can be
401410
passed argument name/value pairs in an invocation of a concrete
402-
`MayaUSDExportCommand` command. There is no "plugin discovery" method
403-
here – the developer/user is responsible for making sure the chaser is
404-
registered.
411+
`MayaUSDExportCommand` command. Export chaser plugins can be discovered
412+
and loaded automatically by declaring the `UsdMaya:ExportChaserPlugin`
413+
metadata in their `plugInfo.json`:
414+
415+
```json
416+
"Info": {
417+
"UsdMaya": {
418+
"ExportChaserPlugin": {}
419+
}
420+
}
421+
```
405422
406423
For example the pxr plug-in provides one such chaser plugin called
407424
`AlembicChaser` to try to make integrating USD into Alembic-heavy

lib/mayaUsd/fileio/chaser/exportChaserRegistry.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ bool UsdMayaExportChaserRegistry::RegisterFactory(
6767
UsdMayaExportChaserRefPtr
6868
UsdMayaExportChaserRegistry::Create(const std::string& name, const FactoryContext& context) const
6969
{
70+
UsdMaya_RegistryHelper::LoadExportChaserPlugins();
7071
TfRegistryManager::GetInstance().SubscribeTo<UsdMayaExportChaserRegistry>();
72+
7173
if (UsdMayaExportChaserRegistry::FactoryFn fn = _factoryRegistry[name]) {
7274
return TfCreateRefPtr(fn(context));
7375
} else {

lib/mayaUsd/fileio/chaser/importChaserRegistry.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ bool UsdMayaImportChaserRegistry::RegisterFactory(const char* name, FactoryFn fn
8080
UsdMayaImportChaserRefPtr
8181
UsdMayaImportChaserRegistry::Create(const char* name, const FactoryContext& context) const
8282
{
83+
UsdMaya_RegistryHelper::LoadImportChaserPlugins();
8384
TfRegistryManager::GetInstance().SubscribeTo<UsdMayaImportChaserRegistry>();
85+
8486
if (UsdMayaImportChaserRegistry::FactoryFn fn = _factoryImportRegistry[name]) {
8587
return TfCreateRefPtr(fn(context));
8688
} else {

lib/mayaUsd/fileio/registryHelper.cpp

Lines changed: 56 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ TF_DEFINE_PRIVATE_TOKENS(
4242
(UsdMaya)
4343
(ShadingModePlugin)
4444
(JobContextPlugin)
45+
(ImportChaserPlugin)
46+
(ExportChaserPlugin)
4547
);
4648
// clang-format on
4749

@@ -140,6 +142,42 @@ static bool _HasMayaPlugin(
140142
return true;
141143
}
142144

145+
static void _FindAndLoadUsdMayaPlugins(const TfToken& pluginKey)
146+
{
147+
const std::vector<TfToken> scope { _tokens->UsdMaya, pluginKey };
148+
149+
for (const auto& plug : PlugRegistry::GetInstance().GetAllPlugins()) {
150+
std::string mayaPlugin;
151+
if (_HasMayaPlugin(plug, scope, &mayaPlugin)) {
152+
if (!mayaPlugin.empty()) {
153+
TF_DEBUG(PXRUSDMAYA_REGISTRY)
154+
.Msg(
155+
"Found %s %s: Loading via Maya API %s.\n",
156+
pluginKey.GetText(),
157+
plug->GetName().c_str(),
158+
mayaPlugin.c_str());
159+
std::string loadPluginCmd
160+
= TfStringPrintf("loadPlugin -quiet %s", mayaPlugin.c_str());
161+
if (MGlobal::executeCommand(loadPluginCmd.c_str())) {
162+
// Need to ensure Python script modules are loaded
163+
// properly for this library (Maya's loadPlugin will not
164+
// load script modules like TfDlopen would).
165+
TfScriptModuleLoader::GetInstance().LoadModules();
166+
} else {
167+
TF_CODING_ERROR("Unable to load mayaplugin %s\n", mayaPlugin.c_str());
168+
}
169+
} else {
170+
TF_DEBUG(PXRUSDMAYA_REGISTRY)
171+
.Msg(
172+
"Found %s %s: Loading via USD API.\n",
173+
pluginKey.GetText(),
174+
plug->GetName().c_str());
175+
plug->Load();
176+
}
177+
}
178+
}
179+
}
180+
143181
/* static */
144182
std::string _PluginDictScopeToDebugString(const std::vector<TfToken>& scope)
145183
{
@@ -202,81 +240,29 @@ void UsdMaya_RegistryHelper::FindAndLoadMayaPlug(
202240
/* static */
203241
void UsdMaya_RegistryHelper::LoadShadingModePlugins()
204242
{
205-
static std::once_flag _shadingModesLoaded;
206-
static std::vector<TfToken> scope = { _tokens->UsdMaya, _tokens->ShadingModePlugin };
207-
std::call_once(_shadingModesLoaded, []() {
208-
PlugPluginPtrVector plugins = PlugRegistry::GetInstance().GetAllPlugins();
209-
std::string mayaPlugin;
210-
TF_FOR_ALL(plugIter, plugins)
211-
{
212-
PlugPluginPtr plug = *plugIter;
213-
if (_HasMayaPlugin(plug, scope, &mayaPlugin)) {
214-
if (!mayaPlugin.empty()) {
215-
TF_DEBUG(PXRUSDMAYA_REGISTRY)
216-
.Msg(
217-
"Found shading mode plugin %s: Loading via Maya API %s.\n",
218-
plug->GetName().c_str(),
219-
mayaPlugin.c_str());
220-
std::string loadPluginCmd
221-
= TfStringPrintf("loadPlugin -quiet %s", mayaPlugin.c_str());
222-
if (MGlobal::executeCommand(loadPluginCmd.c_str())) {
223-
// Need to ensure Python script modules are loaded
224-
// properly for this library (Maya's loadPlugin will not
225-
// load script modules like TfDlopen would).
226-
TfScriptModuleLoader::GetInstance().LoadModules();
227-
} else {
228-
TF_CODING_ERROR("Unable to load mayaplugin %s\n", mayaPlugin.c_str());
229-
}
230-
} else {
231-
TF_DEBUG(PXRUSDMAYA_REGISTRY)
232-
.Msg(
233-
"Found shading mode plugin %s: Loading via USD API.\n",
234-
plug->GetName().c_str());
235-
plug->Load();
236-
}
237-
}
238-
}
239-
});
243+
static std::once_flag _loaded;
244+
std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->ShadingModePlugin); });
240245
}
241246

242247
/* static */
243248
void UsdMaya_RegistryHelper::LoadJobContextPlugins()
244249
{
245-
static std::once_flag _jobContextsLoaded;
246-
static std::vector<TfToken> scope = { _tokens->UsdMaya, _tokens->JobContextPlugin };
247-
std::call_once(_jobContextsLoaded, []() {
248-
PlugPluginPtrVector plugins = PlugRegistry::GetInstance().GetAllPlugins();
249-
std::string mayaPlugin;
250-
TF_FOR_ALL(plugIter, plugins)
251-
{
252-
PlugPluginPtr plug = *plugIter;
253-
if (_HasMayaPlugin(plug, scope, &mayaPlugin)) {
254-
if (!mayaPlugin.empty()) {
255-
TF_DEBUG(PXRUSDMAYA_REGISTRY)
256-
.Msg(
257-
"Found job context plugin %s: Loading via Maya API %s.\n",
258-
plug->GetName().c_str(),
259-
mayaPlugin.c_str());
260-
std::string loadPluginCmd
261-
= TfStringPrintf("loadPlugin -quiet %s", mayaPlugin.c_str());
262-
if (MGlobal::executeCommand(loadPluginCmd.c_str())) {
263-
// Need to ensure Python script modules are loaded
264-
// properly for this library (Maya's loadPlugin will not
265-
// load script modules like TfDlopen would).
266-
TfScriptModuleLoader::GetInstance().LoadModules();
267-
} else {
268-
TF_CODING_ERROR("Unable to load mayaplugin %s\n", mayaPlugin.c_str());
269-
}
270-
} else {
271-
TF_DEBUG(PXRUSDMAYA_REGISTRY)
272-
.Msg(
273-
"Found job context plugin %s: Loading via USD API.\n",
274-
plug->GetName().c_str());
275-
plug->Load();
276-
}
277-
}
278-
}
279-
});
250+
static std::once_flag _loaded;
251+
std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->JobContextPlugin); });
252+
}
253+
254+
/* static */
255+
void UsdMaya_RegistryHelper::LoadImportChaserPlugins()
256+
{
257+
static std::once_flag _loaded;
258+
std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->ImportChaserPlugin); });
259+
}
260+
261+
/* static */
262+
void UsdMaya_RegistryHelper::LoadExportChaserPlugins()
263+
{
264+
static std::once_flag _loaded;
265+
std::call_once(_loaded, []() { _FindAndLoadUsdMayaPlugins(_tokens->ExportChaserPlugin); });
280266
}
281267

282268
/* static */

lib/mayaUsd/fileio/registryHelper.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,30 @@ struct UsdMaya_RegistryHelper
7474
/// usdMaya will try to load the "mayaPlugin" when job contexts are first accessed.
7575
static void LoadJobContextPlugins();
7676

77+
/// Searches the plugInfos and looks for ImportChaserPlugin.
78+
///
79+
/// "UsdMaya" : {
80+
/// "ImportChaserPlugin" : {
81+
/// "mayaPlugin" : "myImportChaserPlugin"
82+
/// }
83+
/// }
84+
///
85+
/// At that scope, it expects an optional "mayaPlugin" key.
86+
/// usdMaya will try to load the plugin when import chasers are first accessed.
87+
static void LoadImportChaserPlugins();
88+
89+
/// Searches the plugInfos and looks for ExportChaserPlugin.
90+
///
91+
/// "UsdMaya" : {
92+
/// "ExportChaserPlugin" : {
93+
/// "mayaPlugin" : "myExportChaserPlugin"
94+
/// }
95+
/// }
96+
///
97+
/// At that scope, it expects an optional "mayaPlugin" key.
98+
/// usdMaya will try to load the plugin when export chasers are first accessed.
99+
static void LoadExportChaserPlugins();
100+
77101
/// Searches the plugInfos for metadata dictionaries at the given \p scope,
78102
/// and composes them together.
79103
/// The scope are the nested keys to search through in the plugInfo (for

test/lib/usd/plugin/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ if(IS_MACOSX OR IS_LINUX)
181181
mayaUsd_install_rpath(rpath ${TARGET_NAME})
182182
endif()
183183

184+
# -----------------------------------------------------------------------------
185+
# Plug Plug-in file
186+
# -----------------------------------------------------------------------------
187+
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/infoImportChaser_plugInfo.json"
188+
"${CMAKE_CURRENT_BINARY_DIR}/infoImportChaser/plugInfo.json"
189+
)
184190

185191
# -----------------------------------------------------------------------------
186192
# Plug Plug-ins
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"Plugins": [
3+
{
4+
"Info": {
5+
"UsdMaya": {
6+
"ImportChaserPlugin": {
7+
"mayaPlugin": "usdTestInfoImportChaser"
8+
}
9+
}
10+
},
11+
"Name": "usdTestInfoImportChaser",
12+
"LibraryPath": "@CMAKE_CURRENT_BINARY_DIR@/@CMAKE_SHARED_LIBRARY_PREFIX@usdTestInfoImportChaser@CMAKE_SHARED_LIBRARY_SUFFIX@",
13+
"Type": "library"
14+
}
15+
]
16+
}

test/lib/usd/plugin/nullApiExporter_plugInfo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"Info": {
55
"UsdMaya": {
66
"JobContextPlugin": {},
7+
"ExportChaserPlugin": {},
78
"SchemaApiAdaptor": {
89
"providesTranslator": [
910
"shape"

test/lib/usd/translators/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ mayaUsd_add_test(testUsdImportChaser
232232
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
233233
ENV
234234
"MAYA_PLUG_IN_PATH=${CMAKE_CURRENT_BINARY_DIR}/../plugin"
235-
"${PXR_OVERRIDE_PLUGINPATH_NAME}=${CMAKE_CURRENT_BINARY_DIR}/../plugin"
235+
"${PXR_OVERRIDE_PLUGINPATH_NAME}=${CMAKE_CURRENT_BINARY_DIR}/../plugin/infoImportChaser"
236236
"LD_LIBRARY_PATH=${ADDITIONAL_LD_LIBRARY_PATH}"
237237
)
238238
set_property(TEST testUsdImportChaser APPEND PROPERTY LABELS translators)

test/lib/usd/translators/testUsdExportSchemaApi.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import maya.api.OpenMaya as om
2424
import maya.api.OpenMayaAnim as oma
2525

26-
from pxr import Tf, Gf, Usd
26+
from pxr import Plug, Tf, Gf, Usd
2727

2828
import fixturesUtils
2929

@@ -137,6 +137,24 @@ def testExportJobContextConflicts(self):
137137

138138
cmds.file(f=True, new=True)
139139

140+
def testExportChaserPluginLoading(self):
141+
"""Testing that export chaser plugins can be automatically loaded by mayaUSDExport from plugInfo."""
142+
143+
chaserPlugin = Plug.Registry().GetPluginWithName("usdTestApiWriter")
144+
145+
# Ensure the plugin is not loaded before the test
146+
self.assertIsNotNone(chaserPlugin)
147+
self.assertFalse(chaserPlugin.isLoaded)
148+
149+
cmds.polySphere(r=1)
150+
usdFilePath = os.path.abspath('UsdExportChaserPluginLoading.usda')
151+
cmds.mayaUSDExport(file=usdFilePath, chaser=["NullAPIChaser"])
152+
153+
# Verify the plugin was loaded by the mayaUSDExport call.
154+
self.assertTrue(chaserPlugin.isLoaded)
155+
156+
cmds.file(f=True, new=True)
157+
140158
@unittest.skipUnless(HAS_BULLET and HAS_USDMAYA, 'Requires the Pixar and bullet plugins.')
141159
def testPluginSchemaAdaptors(self):
142160
"""Testing a plugin Schema adaptor in a generic context:"""

0 commit comments

Comments
 (0)