Skip to content

Commit

Permalink
EMSUSD-1571 Manipulate prim schemas
Browse files Browse the repository at this point in the history
Add new UFE helper functions:
- Add the getKnownSchemas helper function to retrieve all known schemas.
- Schemas are categorized by single and multi apply.
- Schemas are described by their plugin name and schema type name.
- Add the applySchemaToPrim and applyMultiSchemaToPrim helper functions.
- Add a getPrimAppliedSchemas helper function to retrieve the applied schemas of a prim.
- Add a getPrimsAppliedSchemas helper function to get all schemas of a set of prims.
- Add a findSchemasByTypeName helper function to find a schema.
- Expose the helper functions to Python.

Add unit tests:
- Add a unit test to verify the schemas helper function.
- Add unit tests for the schema application.

Add a command to manipulate prim schemas:
- Add a mayaUsdSchema command.
- Add undo/redo support to the schema command.
- Document the mayaUsdSchema command and its flags in the Readme.md.
- The -ufe flag to specify the prim UFE paths.
- The -app (-appliedSchemas) flag to retrieve the already applied schemas.
- The -sch (-schema) flag to specify which schema to add to the prims.
- The -in (-instanceName) flag to specify the instance name of a multi-apply schema.
- The -sas (-singleApplicationSchemas) flag to retrieve the list of known single-apply schemas.
- The -mas (-multiApplicationSchemas) flag tto retrieve the list of known multi-apply schemas.
- Add unit tests for the schema command.
- Work around the bug in Maya 2022 Linux regarding parsing multi-use
  flags in commands executed from Python.

Add schemas menu:
- Add a "Add Schema" menu item in the AE "Attributes" menu.
- Create sub-item for each schema, organized by plugin.
- Cleanup and prettify the plugin and schema names.
- Sort plugin and schema names to have consistent menu item order.
- Ask the user for the multi-apply schema instance name.
- Adjust callback metadata to have a nice undo entry name.
  • Loading branch information
pierrebai-adsk committed Nov 15, 2024
1 parent 56d2b24 commit 98018a9
Show file tree
Hide file tree
Showing 17 changed files with 1,239 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/mayaUsd/commands/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ target_sources(${PROJECT_NAME}
editTargetCommand.cpp
layerEditorCommand.cpp
layerEditorWindowCommand.cpp
schemaCommand.cpp
)

set(HEADERS
Expand All @@ -21,6 +22,7 @@ set(HEADERS
editTargetCommand.h
layerEditorCommand.h
layerEditorWindowCommand.h
schemaCommand.h
)

if(CMAKE_UFE_V3_FEATURES_AVAILABLE)
Expand Down
15 changes: 15 additions & 0 deletions lib/mayaUsd/commands/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ in this instance for the mayaUsd plug-in.
| EditTargetCommand | mayaUsdEditTarget | Command to set or get the edit target |
| LayerEditorCommand | mayaUsdLayerEditor | Manipulate layers |
| LayerEditorWindowCommand | mayaUsdLayerEditorWindow | Open or manipulate the layer window |
| SchemaCommand | mayaUsdSchema | Manipulate prim schemas |

Each base command class is documented in the following sections.

Expand Down Expand Up @@ -632,6 +633,20 @@ The purpose of this command is to set the current edit target.
| `-query` | `-q` | noarg | Retrieve the current edit target |
| `-editTarget` | `-et` | string | The name of the target to set with the `-edit` flag |
## `mayaUsdSchema`
The purpose of this command is to query or apply USD schemas to USD prims.
### Command Flags
| Long flag | Short flag | Type | Description |
| --------------------------- | ---------- | -------------- | -------------------------------------------- |
| `-primUfePath` | `-ufe` | string (multi) | The UFE paths to the USD prims |
| `-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 |
| `-singleApplicationSchemas` | `-sas` | noarg | Query the list of known single-apply schemas |
| `-multiApplicationSchemas` | `-mas` | noarg | Query the list of known multi-apply schemas |
## `LayerEditorCommand`
Expand Down
332 changes: 332 additions & 0 deletions lib/mayaUsd/commands/schemaCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
//
// Copyright 2024 Autodesk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or dataied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "schemaCommand.h"

#include <mayaUsd/ufe/Utils.h>

#include <usdUfe/undo/UsdUndoBlock.h>
#include <usdUfe/undo/UsdUndoableItem.h>
#include <usdUfe/utils/schemas.h>

#include <pxr/base/tf/stringUtils.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>

#include <maya/MArgDatabase.h>
#include <maya/MArgList.h>
#include <maya/MGlobal.h>
#include <maya/MStringArray.h>
#include <maya/MSyntax.h>
#include <ufe/path.h>
#include <ufe/pathString.h>

namespace MAYAUSD_NS_DEF {

////////////////////////////////////////////////////////////////////////////
//
// Error message formatting.

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)
{
return PXR_NS::TfStringPrintf(format, prim.GetPath().GetString().c_str(), text.c_str()).c_str();
}

static std::string formatMessage(const char* format, const Ufe::Path& ufePath)
{
return PXR_NS::TfStringPrintf(format, Ufe::PathString::string(ufePath).c_str());
}

////////////////////////////////////////////////////////////////////////////
//
// Command name and flags.

const char SchemaCommand::commandName[] = "mayaUsdSchema";

static const char kPrimUfePathsFlag[] = "ufe";
static const char kPrimUfePathsLongFlag[] = "primUfePath";
static const char kAppliedSchemasFlag[] = "app";
static const char kAppliedSchemasLongFlag[] = "appliedSchemas";
static const char kSchemaFlag[] = "sch";
static const char kSchemaLongFlag[] = "schema";
static const char kInstanceNameFlag[] = "in";
static const char kInstanceNameLongFlag[] = "instanceName";

static const char kSingleApplicationFlag[] = "sas";
static const char kSingleApplicationLongFlag[] = "singleApplicationSchemas";
static const char kMultiApplicationFlag[] = "mas";
static const char kMultiApplicationLongFlag[] = "multiApplicationSchemas";

////////////////////////////////////////////////////////////////////////////
//
// Command data and argument parsing to fill that data.

class SchemaCommand::Data
{
public:
// Parse the Maya argument list and fill the data with it.
MStatus parseArgs(const MArgList& argList);

// Convert the list of UFE paths given to the command to the corresponding USD prims.
std::vector<PXR_NS::UsdPrim> getPrims() const;

// Clears the list of UFE paths given to the command.
// Used to reduce the memory consupmtion once the command has been executed.
void clearPrimPaths() { _primPaths.clear(); }

// Retrieve the schema name or schema instance name given to the command.
const std::string& getSchema() const { return _schema; }
const std::string& getInstanceName() const { return _instanceName; }

// Check if the command is a query or which specific type of query.
bool isQuerying() const { return isQueryingAppliedSchemas() || isQueryingKnownSchemas(); }
bool isQueryingKnownSchemas() const
{
return isQueryingSingleAppSchemas() || isQueryingMultiAppSchemas();
}
bool isQueryingAppliedSchemas() const { return _isQueryingAppliedSchemas; }
bool isQueryingSingleAppSchemas() const { return _singleApplicationSchemas; }
bool isQueryingMultiAppSchemas() const { return _multiApplicationSchemas; }

// Undo and redo data and implementation.
UsdUfe::UsdUndoableItem& getUsdUndoItem() { return _undoData; }
void undo() { _undoData.undo(); }
void redo() { _undoData.redo(); }

private:
void parsePrimPaths(const MArgDatabase& argData);
std::string parseStringArg(const MArgDatabase& argData, const char* argFlag);

std::vector<Ufe::Path> _primPaths;
bool _isQueryingAppliedSchemas { false };
bool _singleApplicationSchemas { false };
bool _multiApplicationSchemas { false };
std::string _schema;
std::string _instanceName;

UsdUfe::UsdUndoableItem _undoData;
};

MStatus SchemaCommand::Data::parseArgs(const MArgList& argList)
{
MStatus status;
MArgDatabase argData(SchemaCommand::createSyntax(), argList, &status);
if (!status)
return status;

_isQueryingAppliedSchemas = argData.isFlagSet(kAppliedSchemasFlag);
_schema = parseStringArg(argData, kSchemaFlag);
_instanceName = parseStringArg(argData, kInstanceNameFlag);
_singleApplicationSchemas = argData.isFlagSet(kSingleApplicationFlag);
_multiApplicationSchemas = argData.isFlagSet(kMultiApplicationFlag);

parsePrimPaths(argData);

return status;
}

std::string SchemaCommand::Data::parseStringArg(const MArgDatabase& argData, const char* argFlag)
{
if (!argData.isFlagSet(argFlag))
return {};

MString stringVal;
argData.getFlagArgument(argFlag, 0, stringVal);
return stringVal.asChar();
}

void SchemaCommand::Data::parsePrimPaths(const MArgDatabase& argData)
{
_primPaths.clear();

const unsigned int flagCount = argData.numberOfFlagUses(kPrimUfePathsLongFlag);
for (unsigned int flagIndex = 0; flagIndex < flagCount; ++flagIndex) {
MArgList argList;
argData.getFlagArgumentList(kPrimUfePathsLongFlag, flagIndex, argList);
const unsigned int argCount = argList.length();
for (unsigned int argIndex = 0; argIndex < argCount; ++argIndex) {
const std::string arg = argList.asString(argIndex).asChar();
if (arg.size() <= 0)
continue;
_primPaths.push_back(Ufe::PathString::path(arg));
}
}
}

std::vector<PXR_NS::UsdPrim> SchemaCommand::Data::getPrims() const
{
std::vector<PXR_NS::UsdPrim> prims;

for (const Ufe::Path& ufePath : _primPaths) {
PXR_NS::UsdPrim prim = ufe::ufePathToPrim(ufePath);
if (!prim)
throw std::runtime_error(formatMessage("Prim path \"%s\" is invalid", ufePath));
prims.push_back(prim);
}

return prims;
}

////////////////////////////////////////////////////////////////////////////
//
// Command creation and syntax.

void* SchemaCommand::creator() { return static_cast<MPxCommand*>(new SchemaCommand()); }

SchemaCommand::SchemaCommand()
: _data(std::make_unique<SchemaCommand::Data>())
{
}

MSyntax SchemaCommand::createSyntax()
{
MSyntax syntax;

syntax.setObjectType(MSyntax::kNone, 0, 0);

syntax.addFlag(kPrimUfePathsFlag, kPrimUfePathsLongFlag, MSyntax::kString);
syntax.makeFlagMultiUse(kPrimUfePathsFlag);

syntax.addFlag(kAppliedSchemasFlag, kAppliedSchemasLongFlag);

syntax.addFlag(kSchemaFlag, kSchemaLongFlag, MSyntax::kString);
syntax.addFlag(kInstanceNameFlag, kInstanceNameLongFlag, MSyntax::kString);

syntax.addFlag(kSingleApplicationFlag, kSingleApplicationLongFlag);
syntax.addFlag(kMultiApplicationFlag, kMultiApplicationLongFlag);

return syntax;
}

bool SchemaCommand::isUndoable() const { return !_data->isQuerying(); }

////////////////////////////////////////////////////////////////////////////
//
// Command execution.

MStatus SchemaCommand::handleAppliedSchemas()
{
std::set<PXR_NS::TfToken> allSchemas = UsdUfe::getPrimsAppliedSchemas(_data->getPrims());

MStringArray results;
for (const PXR_NS::TfToken& schema : allSchemas)
results.append(schema.GetString().c_str());
setResult(results);

return MS::kSuccess;
}

MStatus SchemaCommand::handleKnownSchemas()
{
const UsdUfe::KnownSchemas knownSchemas = UsdUfe::getKnownApplicableSchemas();

for (const auto& schema : knownSchemas) {
const bool shouldAppend = schema.second.isMultiApply ? _data->isQueryingMultiAppSchemas()
: _data->isQueryingSingleAppSchemas();
if (shouldAppend)
appendToResult(schema.second.schemaTypeName.GetString().c_str());
}

return MS::kSuccess;
}

MStatus SchemaCommand::handleApplySchema()
{
UsdUfe::UsdUndoBlock undoBlock(&_data->getUsdUndoItem());

const std::string& schemaName = _data->getSchema();
if (schemaName.empty()) {
displayError("No schema given to apply to the prims");
return MS::kInvalidParameter;
}

auto maybeInfo = UsdUfe::findSchemasByTypeName(PXR_NS::TfToken(schemaName));
if (!maybeInfo) {
displayError(formatMessage("Cannot find the schema for the type named \"%s\"", schemaName));
return MS::kInvalidParameter;
}

const PXR_NS::TfType& schemaType = maybeInfo->schemaType;

if (maybeInfo->isMultiApply) {
if (_data->getInstanceName().empty()) {
displayError(formatMessage(
"No schema instance name given for the \"%s\" multi-apply schema", schemaName));
return MS::kInvalidParameter;
}

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));
}
}
} else {
for (PXR_NS::UsdPrim& prim : _data->getPrims()) {
if (!UsdUfe::applySchemaToPrim(prim, schemaType)) {
displayWarning(
formatMessage("Could no apply schema \"%s\" to prim \"%s\"", prim, schemaName));
}
}
}

_data->clearPrimPaths();

return MS::kSuccess;
}

MStatus SchemaCommand::doIt(const MArgList& argList)
{
try {
setCommandString(commandName);
clearResult();

MStatus status = _data->parseArgs(argList);
if (!status)
return status;

if (_data->isQueryingAppliedSchemas())
return handleAppliedSchemas();

if (_data->isQueryingKnownSchemas())
return handleKnownSchemas();

return handleApplySchema();
} catch (const std::exception& exc) {
displayError(exc.what());
return MS::kFailure;
}
}

MStatus SchemaCommand::redoIt()
{
_data->redo();
return MS::kSuccess;
}

MStatus SchemaCommand::undoIt()
{
_data->undo();
return MS::kSuccess;
}

} // namespace MAYAUSD_NS_DEF
Loading

0 comments on commit 98018a9

Please sign in to comment.