-
Notifications
You must be signed in to change notification settings - Fork 203
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
MAYA-127700 - Extend Viewport and Outliner menus to allowing assigning new or existing materials #2896
Changes from 3 commits
3cd2c3a
d936496
73b76ad
dd2ce21
63f3dcf
3f03cd0
1d2e528
c7fbf47
c197028
9dd1a0f
2a2ad7a
e29b14c
f8db282
ad5854c
5433bb5
3a25104
a4c0805
c1b909e
4532344
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,6 +66,7 @@ | |
|
||
#include <algorithm> | ||
#include <cassert> | ||
#include <map> | ||
#include <utility> | ||
#include <vector> | ||
|
||
|
@@ -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 | ||
|
||
|
@@ -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 | ||
|
@@ -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) { | ||
|
@@ -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>()) { | ||
|
@@ -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); | ||
// Expects a string in the format "/path1/path2|Material". | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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. | ||
|
@@ -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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ target_sources(${TARGET_NAME} | |
adskExportCommand.cpp | ||
adskListJobContextsCommand.cpp | ||
adskListShadingModesCommand.cpp | ||
adskMaterialCommands.cpp | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.