From 81b1dceb0aaee2cc568a6925a0e90e0ea1a1406f Mon Sep 17 00:00:00 2001 From: Pierre Baillargeon Date: Mon, 18 Nov 2024 14:25:29 -0500 Subject: [PATCH] EMSUSD-1771 remove schema from prims Add functions to support schema removal in UsdUfe: - Add removeSchemaFromPrim helper function to remove a single-apply schema from a prim. - Add removeMultiSchemaFromPrim helper function to remove a single-apply schema from a prim. - Expose those functions to Python. - Add unit test for the functions. Remove schema command: - Add a -rem (-removeSchema) flag to the schema command. - Implement schema removal in the command. - Update the documentation about the command. - Add unit tests for the removal of schemas via the command. Remove schema menu items: - Add a "Remove Schema" menu item in the "Attributes" menu of the AE. - Add each applied schema of the selected prims to the menu. - Echo to the user the schema commands invoked by the menu items. Fix typo and use resources for text. --- lib/mayaUsd/commands/Readme.md | 1 + lib/mayaUsd/commands/schemaCommand.cpp | 47 +++- lib/mayaUsd/commands/schemaCommand.h | 2 +- lib/usdUfe/python/wrapUtils.cpp | 27 +++ lib/usdUfe/utils/schemas.cpp | 48 +++- lib/usdUfe/utils/schemas.h | 40 +++- plugin/adsk/scripts/mayaUSDRegisterStrings.py | 4 +- plugin/adsk/scripts/mayaUsdMenu.py | 218 +++++++++++++++--- test/lib/testMayaUsdSchemaCommand.py | 51 ++++ test/lib/ufe/testSchemas.py | 55 ++++- 10 files changed, 426 insertions(+), 67 deletions(-) diff --git a/lib/mayaUsd/commands/Readme.md b/lib/mayaUsd/commands/Readme.md index c9c492b9ce..5f7194ef63 100644 --- a/lib/mayaUsd/commands/Readme.md +++ b/lib/mayaUsd/commands/Readme.md @@ -645,6 +645,7 @@ It takes as its main arguments a list of UFE paths. | `-appliedSchemas` | `-app` | noarg | Query which schemas the prims have in common | | `-schema` | `-sch` | string | The schema type name to apply to the prims | | `-instanceName` | `-in` | string | The instance name for multi-apply schema | +| `-removeSchema` | `-rem` | noarg | Remove the schema instead of applying it | | `-singleApplicationSchemas` | `-sas` | noarg | Query the list of known single-apply schemas | | `-multiApplicationSchemas` | `-mas` | noarg | Query the list of known multi-apply schemas | diff --git a/lib/mayaUsd/commands/schemaCommand.cpp b/lib/mayaUsd/commands/schemaCommand.cpp index 5be24927ff..0f83ceb840 100644 --- a/lib/mayaUsd/commands/schemaCommand.cpp +++ b/lib/mayaUsd/commands/schemaCommand.cpp @@ -45,9 +45,14 @@ static MString formatMessage(const char* format, const std::string& text) return PXR_NS::TfStringPrintf(format, text.c_str()).c_str(); } -static MString formatMessage(const char* format, PXR_NS::UsdPrim& prim, const std::string& text) +static MString formatMessage( + const char* format, + const char* action, + PXR_NS::UsdPrim& prim, + const std::string& text) { - return PXR_NS::TfStringPrintf(format, prim.GetPath().GetString().c_str(), text.c_str()).c_str(); + return PXR_NS::TfStringPrintf(format, action, prim.GetPath().GetString().c_str(), text.c_str()) + .c_str(); } static std::string formatMessage(const char* format, const Ufe::Path& ufePath) @@ -67,6 +72,8 @@ static const char kSchemaFlag[] = "sch"; static const char kSchemaLongFlag[] = "schema"; static const char kInstanceNameFlag[] = "in"; static const char kInstanceNameLongFlag[] = "instanceName"; +static const char kRemoveSchemaFlag[] = "rem"; +static const char kRemoveSchemaLongFlag[] = "removeSchema"; static const char kSingleApplicationFlag[] = "sas"; static const char kSingleApplicationLongFlag[] = "singleApplicationSchemas"; @@ -94,6 +101,9 @@ class SchemaCommand::Data const std::string& getSchema() const { return _schema; } const std::string& getInstanceName() const { return _instanceName; } + // Check if the command is removing a schema. + bool isRemovingSchema() const { return _isRemovingSchema; } + // Check if the command is a query or which specific type of query. bool isQuerying() const { return isQueryingAppliedSchemas() || isQueryingKnownSchemas(); } bool isQueryingKnownSchemas() const @@ -114,6 +124,7 @@ class SchemaCommand::Data std::string parseStringArg(MArgDatabase& argData, const char* argFlag); std::vector _primPaths; + bool _isRemovingSchema { false }; bool _isQueryingAppliedSchemas { false }; bool _singleApplicationSchemas { false }; bool _multiApplicationSchemas { false }; @@ -133,6 +144,7 @@ MStatus SchemaCommand::Data::parseArgs(const MArgList& argList) _isQueryingAppliedSchemas = argData.isFlagSet(kAppliedSchemasFlag); _schema = parseStringArg(argData, kSchemaFlag); _instanceName = parseStringArg(argData, kInstanceNameFlag); + _isRemovingSchema = argData.isFlagSet(kRemoveSchemaFlag); _singleApplicationSchemas = argData.isFlagSet(kSingleApplicationFlag); _multiApplicationSchemas = argData.isFlagSet(kMultiApplicationFlag); @@ -202,6 +214,8 @@ MSyntax SchemaCommand::createSyntax() syntax.addFlag(kSchemaFlag, kSchemaLongFlag, MSyntax::kString); syntax.addFlag(kInstanceNameFlag, kInstanceNameLongFlag, MSyntax::kString); + syntax.addFlag(kRemoveSchemaFlag, kRemoveSchemaLongFlag); + syntax.addFlag(kSingleApplicationFlag, kSingleApplicationLongFlag); syntax.addFlag(kMultiApplicationFlag, kMultiApplicationLongFlag); @@ -240,13 +254,13 @@ MStatus SchemaCommand::handleKnownSchemas() return MS::kSuccess; } -MStatus SchemaCommand::handleApplySchema() +MStatus SchemaCommand::handleApplyOrRemoveSchema() { UsdUfe::UsdUndoBlock undoBlock(&_data->getUsdUndoItem()); const std::string& schemaName = _data->getSchema(); if (schemaName.empty()) { - displayError("No schema given to apply to the prims"); + displayError("No schema given to modify the prims"); return MS::kInvalidParameter; } @@ -265,18 +279,27 @@ MStatus SchemaCommand::handleApplySchema() return MS::kInvalidParameter; } + PXR_NS::TfToken instanceName(_data->getInstanceName()); + + auto func = _data->isRemovingSchema() ? &UsdUfe::removeMultiSchemaFromPrim + : &UsdUfe::applyMultiSchemaToPrim; + const char* action = _data->isRemovingSchema() ? "remove" : "apply"; + for (PXR_NS::UsdPrim& prim : _data->getPrims()) { - if (!UsdUfe::applyMultiSchemaToPrim( - prim, schemaType, PXR_NS::TfToken(_data->getInstanceName()))) { - displayWarning( - formatMessage("Could no apply schema \"%s\" to prim \"%s\"", prim, schemaName)); + if (!func(prim, schemaType, instanceName)) { + displayWarning(formatMessage( + "Could not %s schema \"%s\" to prim \"%s\"", action, prim, schemaName)); } } } else { + auto func = _data->isRemovingSchema() ? &UsdUfe::removeSchemaFromPrim + : &UsdUfe::applySchemaToPrim; + const char* action = _data->isRemovingSchema() ? "remove" : "apply"; + for (PXR_NS::UsdPrim& prim : _data->getPrims()) { - if (!UsdUfe::applySchemaToPrim(prim, schemaType)) { - displayWarning( - formatMessage("Could no apply schema \"%s\" to prim \"%s\"", prim, schemaName)); + if (!func(prim, schemaType)) { + displayWarning(formatMessage( + "Could not %s schema \"%s\" to prim \"%s\"", action, prim, schemaName)); } } } @@ -302,7 +325,7 @@ MStatus SchemaCommand::doIt(const MArgList& argList) if (_data->isQueryingKnownSchemas()) return handleKnownSchemas(); - return handleApplySchema(); + return handleApplyOrRemoveSchema(); } catch (const std::exception& exc) { displayError(exc.what()); return MS::kFailure; diff --git a/lib/mayaUsd/commands/schemaCommand.h b/lib/mayaUsd/commands/schemaCommand.h index 7435cd7500..ca322b3152 100644 --- a/lib/mayaUsd/commands/schemaCommand.h +++ b/lib/mayaUsd/commands/schemaCommand.h @@ -63,7 +63,7 @@ class SchemaCommand : public MPxCommand MStatus handleAppliedSchemas(); MStatus handleKnownSchemas(); - MStatus handleApplySchema(); + MStatus handleApplyOrRemoveSchema(); class Data; std::unique_ptr _data; diff --git a/lib/usdUfe/python/wrapUtils.cpp b/lib/usdUfe/python/wrapUtils.cpp index 839977d772..4154248142 100644 --- a/lib/usdUfe/python/wrapUtils.cpp +++ b/lib/usdUfe/python/wrapUtils.cpp @@ -147,6 +147,29 @@ bool _applyMultiSchemaToPrim( return UsdUfe::applyMultiSchemaToPrim(prim, schemaType, instanceName); } +bool _removeSchemaFromPrim(PXR_NS::UsdPrim& prim, const PXR_NS::TfType& schemaType) +{ + return UsdUfe::removeSchemaFromPrim(prim, schemaType); +} + +bool _removeMultiSchemaFromPrim( + PXR_NS::UsdPrim& prim, + const PXR_NS::TfType& schemaType, + const PXR_NS::TfToken& instanceName) +{ + return UsdUfe::removeMultiSchemaFromPrim(prim, schemaType, instanceName); +} + +std::vector _getPrimAppliedSchemas(const PXR_NS::UsdPrim& prim) +{ + return UsdUfe::getPrimAppliedSchemas(prim); +} + +std::set _getPrimsAppliedSchemas(const std::vector& prims) +{ + return UsdUfe::getPrimsAppliedSchemas(prims); +} + void wrapUtils() { // Because UsdUfe and UFE have incompatible Python bindings that do not @@ -173,5 +196,9 @@ void wrapUtils() def("getKnownApplicableSchemas", _getKnownApplicableSchemas); def("applySchemaToPrim", _applySchemaToPrim); def("applyMultiSchemaToPrim", _applyMultiSchemaToPrim); + def("removeSchemaFromPrim", _removeSchemaFromPrim); + def("removeMultiSchemaFromPrim", _removeMultiSchemaFromPrim); + def("getPrimAppliedSchemas", _getPrimAppliedSchemas); + def("getPrimsAppliedSchemas", _getPrimsAppliedSchemas); def("findSchemasByTypeName", _findSchemasByTypeName); } diff --git a/lib/usdUfe/utils/schemas.cpp b/lib/usdUfe/utils/schemas.cpp index 1ade54dd60..50f523903f 100644 --- a/lib/usdUfe/utils/schemas.cpp +++ b/lib/usdUfe/utils/schemas.cpp @@ -22,9 +22,9 @@ namespace USDUFE_NS_DEF { -namespace { - -} // namespace +//////////////////////////////////////////////////////////////////////////// +// +// Known schemas KnownSchemas getKnownApplicableSchemas() { @@ -69,12 +69,16 @@ std::shared_ptr findSchemasByTypeName(const PXR_NS::TfToken& schemaT return findSchemasByTypeName(schemaTypeName, UsdUfe::getKnownApplicableSchemas()); } +//////////////////////////////////////////////////////////////////////////// +// +// Schema application + bool applySchemaToPrim(PXR_NS::UsdPrim& prim, const PXR_NS::TfType& schemaType) { return prim.ApplyAPI(schemaType); } -bool applySchemaToPrim(PXR_NS::UsdPrim& prim, const SchemaInfo& info) +bool applySchemaInfoToPrim(PXR_NS::UsdPrim& prim, const SchemaInfo& info) { return applySchemaToPrim(prim, info.schemaType); } @@ -87,7 +91,7 @@ bool applyMultiSchemaToPrim( return prim.ApplyAPI(schemaType, instanceName); } -bool applyMultiSchemaToPrim( +bool applyMultiSchemaInfoToPrim( PXR_NS::UsdPrim& prim, const SchemaInfo& info, const PXR_NS::TfToken& instanceName) @@ -95,6 +99,40 @@ bool applyMultiSchemaToPrim( return applyMultiSchemaToPrim(prim, info.schemaType, instanceName); } +//////////////////////////////////////////////////////////////////////////// +// +// Schema removal + +bool removeSchemaFromPrim(PXR_NS::UsdPrim& prim, const PXR_NS::TfType& schemaType) +{ + return prim.RemoveAPI(schemaType); +} + +bool removeSchemaInfoFromPrim(PXR_NS::UsdPrim& prim, const SchemaInfo& info) +{ + return removeSchemaFromPrim(prim, info.schemaType); +} + +bool removeMultiSchemaFromPrim( + PXR_NS::UsdPrim& prim, + const PXR_NS::TfType& schemaType, + const PXR_NS::TfToken& instanceName) +{ + return prim.RemoveAPI(schemaType, instanceName); +} + +bool removeMultiSchemaInfoFromPrim( + PXR_NS::UsdPrim& prim, + const SchemaInfo& info, + const PXR_NS::TfToken& instanceName) +{ + return removeMultiSchemaFromPrim(prim, info.schemaType, instanceName); +} + +//////////////////////////////////////////////////////////////////////////// +// +// Schema query + std::vector getPrimAppliedSchemas(const PXR_NS::UsdPrim& prim) { const PXR_NS::UsdPrimTypeInfo& info = prim.GetPrimTypeInfo(); diff --git a/lib/usdUfe/utils/schemas.h b/lib/usdUfe/utils/schemas.h index 8f4c8036d7..2bd6a5857a 100644 --- a/lib/usdUfe/utils/schemas.h +++ b/lib/usdUfe/utils/schemas.h @@ -73,7 +73,7 @@ bool applySchemaToPrim(PXR_NS::UsdPrim& prim, const PXR_NS::TfType& schemaType); /// @param info the schema info to apply. /// @return true if the application succeeded. USDUFE_PUBLIC -bool applySchemaToPrim(PXR_NS::UsdPrim& prim, const SchemaInfo& info); +bool applySchemaInfoToPrim(PXR_NS::UsdPrim& prim, const SchemaInfo& info); /// @brief apply the given multi-apply schema type to the given prim. /// @param prim the prim to receive the schema. @@ -92,7 +92,43 @@ bool applyMultiSchemaToPrim( /// @param instanceName the unique name of the new schema application. /// @return true if the application succeeded. USDUFE_PUBLIC -bool applyMultiSchemaToPrim( +bool applyMultiSchemaInfoToPrim( + PXR_NS::UsdPrim& prim, + const SchemaInfo& info, + const PXR_NS::TfToken& instanceName); + +/// @brief remove the given single-apply schema type from the given prim. +/// @param prim the prim to lose the schema. +/// @param schemaType the schema type to remove. +/// @return true if the removal succeeded. +USDUFE_PUBLIC +bool removeSchemaFromPrim(PXR_NS::UsdPrim& prim, const PXR_NS::TfType& schemaType); + +/// @brief remove the given single-apply schema type from the given prim. +/// @param prim the prim to lose the schema. +/// @param info the schema info to remove. +/// @return true if the removal succeeded. +USDUFE_PUBLIC +bool removeSchemaInfoFromPrim(PXR_NS::UsdPrim& prim, const SchemaInfo& info); + +/// @brief remove the given single-apply schema type from the given prim. +/// @param prim the prim to lose the schema. +/// @param schemaType the schema type to remove. +/// @param instanceName the unique name of the new schema application. +/// @return true if the removal succeeded. +USDUFE_PUBLIC +bool removeMultiSchemaFromPrim( + PXR_NS::UsdPrim& prim, + const PXR_NS::TfType& schemaType, + const PXR_NS::TfToken& instanceName); + +/// @brief remove the given single-apply schema type from the given prim. +/// @param prim the prim to lose the schema. +/// @param info the schema info to remove. +/// @param instanceName the unique name of the new schema application. +/// @return true if the removal succeeded. +USDUFE_PUBLIC +bool removeMultiSchemaInfoFromPrim( PXR_NS::UsdPrim& prim, const SchemaInfo& info, const PXR_NS::TfToken& instanceName); diff --git a/plugin/adsk/scripts/mayaUSDRegisterStrings.py b/plugin/adsk/scripts/mayaUSDRegisterStrings.py index dff97753ab..fae504d55a 100644 --- a/plugin/adsk/scripts/mayaUSDRegisterStrings.py +++ b/plugin/adsk/scripts/mayaUSDRegisterStrings.py @@ -139,9 +139,11 @@ "kShareStageAnn": "Toggle sharable on and off to sandbox your changes and create a new stage", "kShowArrayAttributes": "Show Array Attributes", "kAddSchemaMenuItem": "Add Schema", + "kRemoveSchemaMenuItem": "Remove Schema", "kAddSchemaInstanceTitle": "Add Schema Instance", - "kAddSchemaInstanceMesage": "%s Schema Instance Name", + "kAddSchemaInstanceMessage": "%s Schema Instance Name", "kCannotApplySchemaWarning": 'Cannot apply schema "%s": no USD prim are currently selected.', + "kCannotRemoveSchemaWarning": 'Cannot remove schema "%s": no USD prim are currently selected.', "kUSDPointInstancesPickMode_PointInstancer": "Point Instancer", "kUSDPointInstancesPickMode_PointInstancerAnn": "Selection mode for all prims set as point instances.", "kUSDPointInstancesPickMode_Instances": "Instances", diff --git a/plugin/adsk/scripts/mayaUsdMenu.py b/plugin/adsk/scripts/mayaUsdMenu.py index e6ad50a7d3..9fc8c24626 100644 --- a/plugin/adsk/scripts/mayaUsdMenu.py +++ b/plugin/adsk/scripts/mayaUsdMenu.py @@ -9,6 +9,11 @@ from mayaUSDRegisterStrings import getMayaUsdString + +############################################################################ +# +# Manage pretty names + _pluginNiceNames = { "usdGeom": "Geometry", "usdLux": "Lighting", @@ -36,6 +41,30 @@ def _getNicePluginName(pluginName): return _pluginNiceNames.get(pluginName, pluginName) +def _prettifyMenuItem(pluginName, prettyTypeName): + ''' + Make the schema type name more pretty. + ''' + # Note: Python str removesuffix and removeprefix were only added in Python 3.9, + # so we can't use them because we need to support Python 2. + if prettyTypeName.endswith('API'): + prettyTypeName = prettyTypeName[:-3] + + # Note: make sure that the type name is not the same as the plugin name, + # otherwise we would end-up with an empty name! This happens for example + # for the light schema. One of them is called LightAPI... + if prettyTypeName != pluginName and prettyTypeName.startswith(pluginName): + prettyTypeName = prettyTypeName[len(pluginName):] + + prettyTypeName = mayaUsd.ufe.prettifyName(prettyTypeName) + + return prettyTypeName + + +############################################################################ +# +# Schema and selection queries + def _groupSchemasByPlugins(schemas): ''' Group schemas by the plugin name that contains them and then @@ -59,12 +88,19 @@ def _getUsdItemsInSelection(): return [item for item in ufeSelection if item.runTimeId() == mayaUsd.ufe.getUsdRunTimeId()] +def _getUsdPathsInSelection(): + ''' + Retrieve the UFE paths of the USD items in the selection. + ''' + return [ufe.PathString.string(item.path()) for item in _getUsdItemsInSelection()] + + def _askForInstanceName(prettyTypeName): ''' Ask the user for the multi-apply schema instance name. ''' title = getMayaUsdString("kAddSchemaMenuItem") - message = getMayaUsdString("kAddSchemaInstanceMesage") % prettyTypeName + message = getMayaUsdString("kAddSchemaInstanceMessage") % prettyTypeName cancelLabel = getMayaUsdString("kButtonCancel") AddLabel = getMayaUsdString("kButtonAdd") @@ -78,11 +114,41 @@ def _askForInstanceName(prettyTypeName): return cmds.promptDialog(query=True, text=True) +############################################################################ +# +# Invoke and echo schema commands + +def _quoted(text): + '''Quote text if it is text, taking care to escape quotes.''' + if type(text) is not str: + return str(text) + return '"' + text + '"' + + +def _invokeSchemaCommand(primUfePaths, **kwargs): + ''' + Build the schema command and both run and echo it. + ''' + cmd = 'mayaUsdSchema' + for arg, value in kwargs.items(): + cmd += ' -' + arg + if type(value) is not bool: + cmd +=' ' + _quoted(value) + if primUfePaths: + cmd += ' ' + ' '.join([_quoted(path) for path in primUfePaths]) + cmd += ';' + return cmds.mayaUsdSchema(primUfePaths, **kwargs) + + +############################################################################ +# +# Apply schema + def _applySchemaMenuItemCallback(prettyTypeName, schemaTypeName, isMultiApply, menuItem): ''' Apply the given schema to the prims in the current selection. ''' - primPaths = [ufe.PathString.string(item.path()) for item in _getUsdItemsInSelection()] + primPaths = _getUsdPathsInSelection() if not primPaths: cmds.warning(getMayaUsdString("kCannotApplySchemaWarning") % schemaTypeName) return @@ -91,14 +157,14 @@ def _applySchemaMenuItemCallback(prettyTypeName, schemaTypeName, isMultiApply, m instanceName = _askForInstanceName(prettyTypeName) if not instanceName: return - cmds.mayaUsdSchema(primPaths, schema=schemaTypeName, instanceName=instanceName) + _invokeSchemaCommand(primPaths, schema=schemaTypeName, instanceName=instanceName) else: - cmds.mayaUsdSchema(primPaths, schema=schemaTypeName) + _invokeSchemaCommand(primPaths, schema=schemaTypeName) def _createApplySchemaCommand(prettyTypeName, schemaTypeName, isMultiApply): ''' - Create a menuitem callback for the given argument and with its metadata updated + Create a menuitem callback that will apply the given schema and update its metadata to have a nice undo item entry in the Maya Edit menu. ''' f = partial(_applySchemaMenuItemCallback, prettyTypeName, schemaTypeName, isMultiApply) @@ -110,27 +176,7 @@ def _createApplySchemaCommand(prettyTypeName, schemaTypeName, isMultiApply): return f -def _prettifyMenuItem(pluginName, prettyTypeName): - ''' - Make the schema type name more pretty. - ''' - # Note: Python str removesuffix and removeprefix were only added in Python 3.9, - # so we can't use them because we need to support Python 2. - if prettyTypeName.endswith('API'): - prettyTypeName = prettyTypeName[:-3] - - # Note: make sure that the type name is not the same as the plugin name, - # otherwise we would end-up with an empty name! This happens for example - # for the light schema. One of them is called LightAPI... - if prettyTypeName != pluginName and prettyTypeName.startswith(pluginName): - prettyTypeName = prettyTypeName[len(pluginName):] - - prettyTypeName = mayaUsd.ufe.prettifyName(prettyTypeName) - - return prettyTypeName - - -def _createSchemaMenuItem(pluginMenu, pluginName, schemaTypeName, isMultiApply): +def _createApplySchemaMenuItem(pluginMenu, pluginName, schemaTypeName, isMultiApply): ''' Create a menu item for the schema that will apply it to the selection. We create them in a function to avoid problems with variable capture @@ -141,18 +187,16 @@ def _createSchemaMenuItem(pluginMenu, pluginName, schemaTypeName, isMultiApply): cmds.menuItem( label=prettyTypeName + suffix, parent=pluginMenu, command=_createApplySchemaCommand(prettyTypeName, schemaTypeName, isMultiApply)) + - -def aeAttributesMenuCallback(aeAttributeMenu): - mayaVersion = cmds.about(apiVersion=True) - cmds.menuItem( - divider=True, dividerLabel=getMayaUsdString("kUniversalSceneDescription"), - parent=aeAttributeMenu) - +def _createAddSchemasMenuItem(parentMenu, mayaVersion): + ''' + Create a "Add Schema" menu items with all schemas as sub-items. + ''' hasUSDPrimInSelection = bool(len(_getUsdItemsInSelection())) addMenu = cmds.menuItem( label=getMayaUsdString("kAddSchemaMenuItem"), version=mayaVersion//10000, - subMenu=True, parent=aeAttributeMenu, enable=hasUSDPrimInSelection) + subMenu=True, parent=parentMenu, enable=hasUSDPrimInSelection) # Make sure plugin names are presented in a predictable order, sorted by their pretty names. schemaByPlugins = _groupSchemasByPlugins(usdUfe.getKnownApplicableSchemas()) @@ -167,4 +211,108 @@ def aeAttributesMenuCallback(aeAttributeMenu): for _, schemaTypeName in schemaNames: isMultiApply = schemas[schemaTypeName] - _createSchemaMenuItem(pluginMenu, pluginName, schemaTypeName, isMultiApply) + _createApplySchemaMenuItem(pluginMenu, pluginName, schemaTypeName, isMultiApply) + + +############################################################################ +# +# Remove schema + +def _removeSchemaMenuItemCallback(prettyTypeName, schemaTypeName, instanceName, menuItem): + ''' + Remove the given schema from the prims in the current selection. + ''' + primPaths = _getUsdPathsInSelection() + if not primPaths: + cmds.warning(getMayaUsdString("kCannotRemoveSchemaWarning") % schemaTypeName) + return + + _invokeSchemaCommand(primPaths, schema=schemaTypeName, instanceName=instanceName, rem=True) + + +def _createRemoveSchemaCommand(prettyTypeName, schemaTypeName, instanceName): + ''' + Create a menuitem callback that will remove the given schema and update its metadata + to have a nice undo item entry in the Maya Edit menu. + ''' + f = partial(_removeSchemaMenuItemCallback, prettyTypeName, schemaTypeName, instanceName) + # Update the function metadata so that we get a nice entry in the Maya + # undo menu. + nonBreakSpace = '\xa0' + f.__module__ = ('Remove Schema ' + prettyTypeName).replace(' ', nonBreakSpace) + f.__name__ = '' + return f + + +def _createRemoveSchemaMenuItem(pluginMenu, pluginName, schemaTypeName, instanceName): + ''' + Create a menu item for the schema that will remove it to the selection. + We create them in a function to avoid problems with variable capture + in the lambda. + ''' + prettyTypeName = _prettifyMenuItem(pluginName, schemaTypeName) + if instanceName: + prettyTypeName += ': ' + mayaUsd.ufe.prettifyName(instanceName) + cmds.menuItem( + label=prettyTypeName, parent=pluginMenu, + command=_createRemoveSchemaCommand(prettyTypeName, schemaTypeName, instanceName)) + + +def _createRemoveSchemasMenuItem(parentMenu, mayaVersion): + ''' + Create a "Remove Schema" menu items with all schemas of the given prims. + ''' + schemaTypeNameAndInstanceNames = _invokeSchemaCommand(_getUsdPathsInSelection(), appliedSchemas=True) + + hasSchemaToRemove = bool(len(schemaTypeNameAndInstanceNames)) + addMenu = cmds.menuItem( + label=getMayaUsdString("kRemoveSchemaMenuItem"), version=mayaVersion//10000, + subMenu=True, parent=parentMenu, enable=hasSchemaToRemove) + + if not hasSchemaToRemove: + return + + # Multi-apply schemas are reported as `schemaTypeName:instance-name`, so split the names. + # For single-apply schemas, the split will have a single item, the schema type name. + schemaTypeNameAndInstanceNames = [sch.split(':') for sch in schemaTypeNameAndInstanceNames] + + # Make sure plugin names are presented in a predictable order, sorted by their pretty names. + schemaByPlugins = _groupSchemasByPlugins(usdUfe.getKnownApplicableSchemas()) + pluginNames = sorted([(mayaUsd.ufe.prettifyName(name), name) for name in schemaByPlugins.keys()]) + + for prettyPluginName, pluginName in pluginNames: + schemas = schemaByPlugins[pluginName] + + # Only create the plugin menu if it will have items in it. + hasCreatedMenu = False + + # Make sure the schema names are presented in a predictable order, sorted by their pretty names. + schemaNames = sorted([(_prettifyMenuItem(pluginName, name), name) for name in schemas.keys()]) + + for _, schemaTypeName in schemaNames: + for schAndInst in schemaTypeNameAndInstanceNames: + if schemaTypeName != schAndInst[0]: + continue + + if not hasCreatedMenu: + pluginMenu = cmds.menuItem(label=prettyPluginName, subMenu=True, parent=addMenu) + hasCreatedMenu = True + + instanceName = schAndInst[1] if len(schAndInst) > 1 else '' + _createRemoveSchemaMenuItem(pluginMenu, pluginName, schemaTypeName, instanceName) + + +############################################################################ +# +# Menu creation entry point + +def aeAttributesMenuCallback(aeAttributeMenu): + cmds.menuItem( + divider=True, dividerLabel=getMayaUsdString("kUniversalSceneDescription"), + parent=aeAttributeMenu) + + mayaVersion = cmds.about(apiVersion=True) + + _createAddSchemasMenuItem(aeAttributeMenu, mayaVersion) + _createRemoveSchemasMenuItem(aeAttributeMenu, mayaVersion) + diff --git a/test/lib/testMayaUsdSchemaCommand.py b/test/lib/testMayaUsdSchemaCommand.py index 07d499f1b9..49d9354ad0 100644 --- a/test/lib/testMayaUsdSchemaCommand.py +++ b/test/lib/testMayaUsdSchemaCommand.py @@ -107,6 +107,23 @@ def testApplySchemaCommand(self): self.assertEqual(len(applied), 1) self.assertIn('MaterialBindingAPI', applied) + # Remove the schema and verify it has been removed. + cmds.mayaUsdSchema(primUfePath, schema='MaterialBindingAPI', removeSchema=True) + applied = cmds.mayaUsdSchema(primUfePath, appliedSchemas=True) + self.assertFalse(applied) + + # Verify undo adds the schema. + cmds.undo() + applied = cmds.mayaUsdSchema(primUfePath, appliedSchemas=True) + self.assertTrue(applied) + self.assertEqual(len(applied), 1) + self.assertIn('MaterialBindingAPI', applied) + + # Verify redo removes the schema. + cmds.redo() + applied = cmds.mayaUsdSchema(primUfePath, appliedSchemas=True) + self.assertFalse(applied) + def testApplySchemaToMultiplePrimsCommand(self): ''' @@ -147,6 +164,23 @@ def testApplySchemaToMultiplePrimsCommand(self): self.assertEqual(len(applied), 1) self.assertIn('MaterialBindingAPI', applied) + # Remove the schema and verify it has been removed. + cmds.mayaUsdSchema(primUfePaths, schema='MaterialBindingAPI', removeSchema=True) + applied = cmds.mayaUsdSchema(primUfePaths, appliedSchemas=True) + self.assertFalse(applied) + + # Verify undo adds the schema. + cmds.undo() + applied = cmds.mayaUsdSchema(primUfePaths, appliedSchemas=True) + self.assertTrue(applied) + self.assertEqual(len(applied), 1) + self.assertIn('MaterialBindingAPI', applied) + + # Verify redo removes the schema. + cmds.redo() + applied = cmds.mayaUsdSchema(primUfePaths, appliedSchemas=True) + self.assertFalse(applied) + def testApplyMultiSchemaCommand(self): '''Test the -ufe, -schema, -instanceName and -appliedSchemas flags for a multi-apply schema''' @@ -188,6 +222,23 @@ def testApplyMultiSchemaCommand(self): self.assertEqual(len(applied), 1) self.assertIn('CollectionAPI:this', applied) + # Remove the schema and verify it has been removed. + cmds.mayaUsdSchema(primUfePath, schema='CollectionAPI', instanceName='this', removeSchema=True) + applied = cmds.mayaUsdSchema(primUfePath, appliedSchemas=True) + self.assertFalse(applied) + + # Verify undo adds the schema. + cmds.undo() + applied = cmds.mayaUsdSchema(primUfePath, appliedSchemas=True) + self.assertTrue(applied) + self.assertEqual(len(applied), 1) + self.assertIn('CollectionAPI:this', applied) + + # Verify redo removes the schema. + cmds.redo() + applied = cmds.mayaUsdSchema(primUfePath, appliedSchemas=True) + self.assertFalse(applied) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/test/lib/ufe/testSchemas.py b/test/lib/ufe/testSchemas.py index f69dec341d..140f720648 100644 --- a/test/lib/ufe/testSchemas.py +++ b/test/lib/ufe/testSchemas.py @@ -75,8 +75,8 @@ def testKnownSchemas(self): self.assertIn('CollectionAPI', multiSchemaByPlugins['usd']) - def testApplySingleApplySchema(self): - '''Test applying a single-apply schema to a prim.''' + def testSingleApplySchema(self): + '''Test applying, listing and removing a single-apply schema to a prim.''' stage = Usd.Stage.CreateInMemory() prim = stage.DefinePrim('/Hello') self.assertTrue(prim) @@ -84,10 +84,13 @@ def testApplySingleApplySchema(self): knownSchemas = usdUfe.getKnownApplicableSchemas() singleSchemaByPlugins = self.convertToSchemasByPlugins(knownSchemas, False) - self.assertIn('usdGeom', singleSchemaByPlugins) - self.assertIn('GeomModelAPI', singleSchemaByPlugins['usdGeom']) + expectedPluginName = 'usdGeom' + expectedSchemaTypeName = 'GeomModelAPI' + + self.assertIn(expectedPluginName, singleSchemaByPlugins) + self.assertIn(expectedSchemaTypeName, singleSchemaByPlugins['usdGeom']) - modelSchemaType = singleSchemaByPlugins['usdGeom']['GeomModelAPI'] + modelSchemaType = singleSchemaByPlugins[expectedPluginName][expectedSchemaTypeName] self.assertTrue(modelSchemaType) self.assertFalse(prim.HasAPI(modelSchemaType)) @@ -95,8 +98,20 @@ def testApplySingleApplySchema(self): self.assertTrue(result) self.assertTrue(prim.HasAPI(modelSchemaType)) - def testApplyMultiApplySchema(self): - '''Test applying a multi-apply schema to a prim.''' + schemaTypeNames = usdUfe.getPrimAppliedSchemas(prim) + self.assertTrue(schemaTypeNames) + self.assertIn(expectedSchemaTypeName, schemaTypeNames) + + result = usdUfe.removeSchemaFromPrim(prim, modelSchemaType) + self.assertTrue(result) + self.assertFalse(prim.HasAPI(modelSchemaType)) + + schemaTypeNames = usdUfe.getPrimAppliedSchemas(prim) + self.assertNotIn(expectedSchemaTypeName, schemaTypeNames) + + + def testMultiApplySchema(self): + '''Test applying. listing and removing a multi-apply schema to a prim.''' stage = Usd.Stage.CreateInMemory() prim = stage.DefinePrim('/Hello') self.assertTrue(prim) @@ -104,13 +119,17 @@ def testApplyMultiApplySchema(self): knownSchemas = usdUfe.getKnownApplicableSchemas() multiSchemaByPlugins = self.convertToSchemasByPlugins(knownSchemas, True) - self.assertIn('usd', multiSchemaByPlugins) - self.assertIn('CollectionAPI', multiSchemaByPlugins['usd']) + expectedPluginName = 'usd' + expectedSchemaTypeName = 'CollectionAPI' + instanceName = 'mine' + fullSchemaName = expectedSchemaTypeName + ':' + instanceName + + self.assertIn(expectedPluginName, multiSchemaByPlugins) + self.assertIn(expectedSchemaTypeName, multiSchemaByPlugins[expectedPluginName]) - collectionSchemaType = multiSchemaByPlugins['usd']['CollectionAPI'] + collectionSchemaType = multiSchemaByPlugins[expectedPluginName][expectedSchemaTypeName] self.assertTrue(collectionSchemaType) - instanceName = 'mine' self.assertFalse(prim.HasAPI(collectionSchemaType)) self.assertFalse(prim.HasAPI(collectionSchemaType, instanceName=instanceName)) result = usdUfe.applyMultiSchemaToPrim(prim, collectionSchemaType, instanceName) @@ -118,6 +137,20 @@ def testApplyMultiApplySchema(self): self.assertTrue(prim.HasAPI(collectionSchemaType)) self.assertTrue(prim.HasAPI(collectionSchemaType, instanceName=instanceName)) + schemaTypeNames = usdUfe.getPrimAppliedSchemas(prim) + self.assertTrue(schemaTypeNames) + self.assertIn(fullSchemaName, schemaTypeNames) + + result = usdUfe.removeMultiSchemaFromPrim(prim, collectionSchemaType, instanceName) + self.assertTrue(result) + self.assertFalse(prim.HasAPI(collectionSchemaType)) + self.assertFalse(prim.HasAPI(collectionSchemaType, instanceName=instanceName)) + + schemaTypeNames = usdUfe.getPrimAppliedSchemas(prim) + self.assertNotIn(expectedSchemaTypeName, schemaTypeNames) + self.assertNotIn(fullSchemaName, schemaTypeNames) + + def testFindSchemaInfo(self): '''Test that findSchemasByTypeName works.''' unknown = usdUfe.findSchemasByTypeName('unknown')