Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAYA-127700 - Extend Viewport and Outliner menus to allowing assigning new or existing materials #2896

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 93 additions & 145 deletions lib/mayaUsd/ufe/UsdContextOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@

#include <algorithm>
#include <cassert>
#include <map>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -144,16 +145,8 @@ static constexpr char kAssignNewMaterialItem[] = "Assign New Material";
static constexpr char kAssignNewMaterialLabel[] = "Assign New Material";
static constexpr char kAddNewMaterialItem[] = "Add New Material";
static constexpr char kAddNewMaterialLabel[] = "Add New Material";
static constexpr char kAssignNewUsdMaterialItem[] = "USD Material";
static constexpr char kAssignNewUsdMaterialLabel[] = "USD";
static constexpr char kAssignNewMaterialXMaterialItem[] = "MaterialX Material";
static constexpr char kAssignNewMaterialXMaterialLabel[] = "MaterialX";
static constexpr char kAssignNewArnoldMaterialItem[] = "Arnold Material";
static constexpr char kAssignNewArnoldMaterialLabel[] = "Arnold";
static constexpr char kAssignNewUsdPreviewSurfaceMaterialItem[] = "UsdPreviewSurface";
static constexpr char kAssignNewUsdPreviewSurfaceMaterialLabel[] = "USD Preview Surface";
static constexpr char kAssignNewAIStandardSurfaceMaterialItem[] = "arnold:standard_surface";
static constexpr char kAssignNewAIStandardSurfaceMaterialLabel[] = "AI Standard Surface";
static constexpr char kAssignExistingMaterialItem[] = "Assign Existing Material";
static constexpr char kAssignExistingMaterialLabel[] = "Assign Existing Material";
#endif
#endif

Expand Down Expand Up @@ -201,63 +194,6 @@ struct WaitCursor
~WaitCursor() { MGlobal::executeCommand("waitCursor -state 0"); }
};

#if UFE_PREVIEW_VERSION_NUM >= 4010
//! \brief This check has a 3 seconds slowdown to load all Sdr nodes in the registry. We do it in
/// advance in order to not have this 3 seconds delay when the "Assign New Material" submenu
/// is built for the first time.
bool _hasArnoldShaders()
{
auto findArnold = []() {
const auto& sdrRegistry = PXR_NS::SdrRegistry::GetInstance();
auto sourceTypes = sdrRegistry.GetAllNodeSourceTypes();
return std::find(sourceTypes.cbegin(), sourceTypes.cend(), TfToken("arnold"))
!= sourceTypes.cend();
};
static const bool kHasArnoldShaders = findArnold();
return kHasArnoldShaders;
};

struct MxShaderMenuEntry
{
MxShaderMenuEntry(const std::string& label, const std::string& identifier)
: _label(label)
, _identifier(identifier)
{
}
const std::string _label;
const std::string& _identifier;
};
typedef std::vector<MxShaderMenuEntry> MxShaderMenuEntryVec;

const MxShaderMenuEntryVec& getMaterialXSurfaceShaders()
{
static MxShaderMenuEntryVec mxSurfaceShaders;
static bool initialized = false;
if (!initialized) {
auto& sdrRegistry = PXR_NS::SdrRegistry::GetInstance();
// Here is a list of nodes we know work fine as starting materials for the contextual menu.
// We might add discovery code later, but this discovery code will have the difficult task
// of filtering out:
// - utility nodes like ND_add_surfaceshader
// - basic building blocks like ND_thin_surface
// - shaders that exist only as pure definitions like ND_disney_bsdf_2015_surface
static const std::vector<std::pair<std::string, std::string>> vettedSurfaces
= { { "ND_standard_surface_surfaceshader", "Standard Surface" },
{ "ND_gltf_pbr_surfaceshader", "glTF PBR" },
{ "ND_UsdPreviewSurface_surfaceshader", "USD Preview Surface" } };
for (auto&& info : vettedSurfaces) {
auto shaderDef = sdrRegistry.GetShaderNodeByIdentifier(TfToken(info.first));
if (!shaderDef) {
continue;
}
mxSurfaceShaders.emplace_back(info.second, info.first);
}
initialized = true;
}
return mxSurfaceShaders;
}
#endif

#ifdef UFE_V3_FEATURES_AVAILABLE
//! \brief Create a Prim and select it:
class UsdUndoAddNewPrimAndSelectCommand : public Ufe::CompositeUndoableCommand
Expand Down Expand Up @@ -947,58 +883,6 @@ Ufe::ContextOps::Items UsdContextOps::getItems(const Ufe::ContextOps::ItemPath&
if (!fIsAGatewayType) {
// Top level item - Bind/unbind existing materials
bool materialSeparatorsAdded = false;
if (sceneItemSupportsShading(fItem)) {
// Show bind menu if there is at least one bindable material in the stage.
//
// TODO: Show only materials that are inside of the asset's namespace otherwise
// there will be "refers to a path outside the scope" errors. See
// https://groups.google.com/g/usd-interest/c/dmjV5bQBKIo/m/LeozZ3k6BAAJ
// This might help restrict the stage traversal scope and improve performance.
//
// For completeness, and to point out that material assignments are complex:
//
// TODO: Introduce the "rendering purpose" concept
// TODO: Introduce material binding via collections API
//
// Find materials in the global selection. Either directly selected or a direct
// child of the selection. This way we limit how many items we traverse in search of
// something to bind.
if (!materialSeparatorsAdded) {
items.emplace_back(Ufe::ContextItem::kSeparator);
materialSeparatorsAdded = true;
}
bool foundMaterialItem = false;
if (auto globalSn = Ufe::GlobalSelection::get()) {
for (auto&& selItem : *globalSn) {
UsdSceneItem::Ptr usdItem
= std::dynamic_pointer_cast<UsdSceneItem>(selItem);
if (!usdItem) {
continue;
}
UsdShadeMaterial material(usdItem->prim());
if (material) {
foundMaterialItem = true;
break;
}
for (auto&& usdChild : usdItem->prim().GetChildren()) {
UsdShadeMaterial material(usdChild);
if (material) {
foundMaterialItem = true;
break;
}
}
if (foundMaterialItem) {
break;
}
}
if (foundMaterialItem) {
items.emplace_back(
BindMaterialUndoableCommand::commandName,
BindMaterialUndoableCommand::commandName,
Ufe::ContextItem::kHasChildren);
}
}
}
#if UFE_PREVIEW_VERSION_NUM >= 4010
if (sceneItemSupportsShading(fItem)) {
if (!materialSeparatorsAdded) {
Expand All @@ -1009,6 +893,10 @@ Ufe::ContextOps::Items UsdContextOps::getItems(const Ufe::ContextOps::ItemPath&
kAssignNewMaterialItem,
kAssignNewMaterialLabel,
Ufe::ContextItem::kHasChildren);
items.emplace_back(
kAssignExistingMaterialItem,
kAssignExistingMaterialLabel,
Ufe::ContextItem::kHasChildren);
}
#endif
if (fItem->prim().HasAPI<UsdShadeMaterialBindingAPI>()) {
Expand Down Expand Up @@ -1154,33 +1042,78 @@ Ufe::ContextOps::Items UsdContextOps::getItems(const Ufe::ContextOps::ItemPath&
}
}
#if UFE_PREVIEW_VERSION_NUM >= 4010
} else if (
itemPath.size() == 1u
&& (itemPath[0] == kAssignNewMaterialItem || itemPath[0] == kAddNewMaterialItem)) {
items.emplace_back(
kAssignNewUsdMaterialItem,
kAssignNewUsdMaterialLabel,
Ufe::ContextItem::kHasChildren);
items.emplace_back(
kAssignNewMaterialXMaterialItem,
kAssignNewMaterialXMaterialLabel,
Ufe::ContextItem::kHasChildren);
if (_hasArnoldShaders()) {
items.emplace_back(
kAssignNewArnoldMaterialItem,
kAssignNewArnoldMaterialLabel,
Ufe::ContextItem::kHasChildren);
} else if (itemPath[0] == kAssignNewMaterialItem || itemPath[0] == kAddNewMaterialItem) {
std::multimap<std::string, MString> renderersAndMaterials;
MStringArray materials;
MGlobal::executeCommand("mayaUsdGetMaterialsFromRenderers", materials);

for (const auto& materials : materials) {
// Expects a string in the format "renderer/Material Name|Material Identifier".
MStringArray rendererAndMaterial;
MStatus status = materials.split('/', rendererAndMaterial);
if (status == MS::kSuccess && rendererAndMaterial.length() == 2) {
renderersAndMaterials.emplace(
std::string(rendererAndMaterial[0].asChar()), rendererAndMaterial[1]);
}
}
} else if (itemPath.size() == 2u && itemPath[1] == kAssignNewUsdMaterialItem) {
items.emplace_back(
kAssignNewUsdPreviewSurfaceMaterialItem, kAssignNewUsdPreviewSurfaceMaterialLabel);
} else if (itemPath.size() == 2u && itemPath[1] == kAssignNewMaterialXMaterialItem) {
for (auto&& menuEntry : getMaterialXSurfaceShaders()) {
items.emplace_back(menuEntry._identifier, menuEntry._label);

if (itemPath.size() == 1u) {
// Populate list of known renderers (first menu level).
for (auto it = renderersAndMaterials.begin(), end = renderersAndMaterials.end();
it != end;
it = renderersAndMaterials.upper_bound(it->first)) {
items.emplace_back(it->first, it->first, Ufe::ContextItem::kHasChildren);
}
} else if (itemPath.size() == 2u) {
// Populate list of materials for a given renderer (second menu level).
const auto range = renderersAndMaterials.equal_range(itemPath[1]);
for (auto it = range.first; it != range.second; ++it) {
MStringArray materialAndIdentifier;
// Expects a string in the format "Material Name|MaterialIdentifer".
MStatus status = it->second.split('|', materialAndIdentifier);
if (status == MS::kSuccess && materialAndIdentifier.length() == 2) {
items.emplace_back(
materialAndIdentifier[1].asChar(), materialAndIdentifier[0].asChar());
}
}
}
} else if (itemPath[0] == kAssignExistingMaterialItem) {
std::multimap<std::string, MString> pathsAndMaterials;
MStringArray materials;
MString script;
script.format(
"mayaUsdGetMaterialsInStage \"^1s\"",
Ufe::PathString::string(fItem->path()).c_str());
MGlobal::executeCommand(script, materials);

for (const auto& material : materials) {
MStringArray pathAndMaterial;
MStatus status = material.split('/', pathAndMaterial);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not a reverse find for the last "/" that will allow you to split the string in two at the right location?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Looks like MString::rindex will help there.

// Expects a string in the format "/path1/path2|Material".
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that comment correct? I don't see any splitting on the "|" in the below code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo, thanks :)

if (status == MS::kFailure || pathAndMaterial.length() < 2) {
continue;
}

MString pathToMaterial = "";
for (int i = 0; i < pathAndMaterial.length() - 1; i++) {
pathToMaterial += "/" + pathAndMaterial[i];
}
pathsAndMaterials.emplace(std::string(pathToMaterial.asChar()), material);
}

if (itemPath.size() == 1u) {
// Populate list of paths to materials (first menu level).
for (auto it = pathsAndMaterials.begin(), end = pathsAndMaterials.end(); it != end;
it = pathsAndMaterials.upper_bound(it->first)) {
items.emplace_back(it->first, it->first, Ufe::ContextItem::kHasChildren);
}
} else if (itemPath.size() == 2u) {
// Populate list of to materials for given path (second menu level).
const auto range = pathsAndMaterials.equal_range(itemPath[1]);
for (auto it = range.first; it != range.second; ++it) {
items.emplace_back(it->second.asChar(), it->second.asChar());
}
}
} else if (itemPath.size() == 2u && itemPath[1] == kAssignNewArnoldMaterialItem) {
items.emplace_back(
kAssignNewAIStandardSurfaceMaterialItem, kAssignNewAIStandardSurfaceMaterialLabel);
#endif
}
#endif
Expand Down Expand Up @@ -1319,7 +1252,7 @@ Ufe::UndoableCommand::Ptr UsdContextOps::doOpCmd(const ItemPath& itemPath)
return std::make_shared<UnbindMaterialUndoableCommand>(fItem->prim());
#if UFE_PREVIEW_VERSION_NUM >= 4010
} else if (itemPath.size() == 3u && itemPath[0] == kAssignNewMaterialItem) {
// Make a copy so that we don't change to user's original selection
// Make a copy so that we don't change the user's original selection.
Ufe::Selection sceneItems(*Ufe::GlobalSelection::get());
// As per UX' wishes, we add the item that was right-clicked,
// regardless of its selection state.
Expand All @@ -1331,6 +1264,21 @@ Ufe::UndoableCommand::Ptr UsdContextOps::doOpCmd(const ItemPath& itemPath)
} else if (itemPath.size() == 3u && itemPath[0] == kAddNewMaterialItem) {
return std::make_shared<InsertChildAndSelectCommand>(
UsdUndoAddNewMaterialCommand::create(fItem, itemPath[2]));
} else if (itemPath.size() == 3u && itemPath[0] == kAssignExistingMaterialItem) {
std::shared_ptr<Ufe::CompositeUndoableCommand> compositeCmd;
Ufe::Selection sceneItems(*Ufe::GlobalSelection::get());
sceneItems.append(fItem);
for (auto& sceneItem : sceneItems) {
UsdPrim compatiblePrim = BindMaterialUndoableCommand::CompatiblePrim(sceneItem);
if (compatiblePrim) {
if (!compositeCmd) {
compositeCmd = std::make_shared<Ufe::CompositeUndoableCommand>();
}
compositeCmd->append(std::make_shared<BindMaterialUndoableCommand>(
compatiblePrim, SdfPath(itemPath[2])));
}
}
return compositeCmd;
#endif
}
#endif
Expand Down
1 change: 1 addition & 0 deletions plugin/adsk/plugin/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ target_sources(${TARGET_NAME}
adskExportCommand.cpp
adskListJobContextsCommand.cpp
adskListShadingModesCommand.cpp
adskMaterialCommands.cpp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add this to the list TARGET_NAME only is UFE_FOUND is true. This will fix the ecg-mayausd-branch-preflight-2019-no-ufe-windows build.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliant, thanks again! Hadn't even started to look into that issue and you already offer a solution :)

adskStageLoadUnloadCommands.cpp
geomNode.cpp
importTranslator.cpp
Expand Down
Loading