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

Add contextual menu to bind materials #2287

Merged
merged 5 commits into from
Apr 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
280 changes: 271 additions & 9 deletions lib/mayaUsd/ufe/UsdContextOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <pxr/base/tf/diagnostic.h>
#include <pxr/base/tf/getenv.h>
#include <pxr/pxr.h>
#include <pxr/usd/sdf/fileFormat.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/sdf/reference.h>
#include <pxr/usd/usd/common.h>
Expand Down Expand Up @@ -357,21 +358,57 @@ class ToggleInstanceableStateCommand : public Ufe::UndoableCommand
bool _instanceable;
};

const char* selectUSDFileScript = R"(
const char* selectUSDFileScriptPre = R"mel(
global proc string SelectUSDFileForAddReference()
{
string $result[] = `fileDialog2
-fileMode 1
-caption "Add Reference to USD Prim"
-fileFilter "USD Files (*.usd *.usda *.usdc);;*.usd;;*.usda;;*.usdc"`;
-fileFilter "USD Files )mel";

const char* selectUSDFileScriptPost = R"mel("`;

if (0 == size($result))
return "";
else
return $result[0];
}
SelectUSDFileForAddReference();
)";
)mel";

// Ask SDF for all supported extensions:
const char* _selectUSDFileScript()
{

static std::string commandString;

if (commandString.empty()) {
// This is an interactive call from the main UI thread. No need for SMP protections.
commandString = selectUSDFileScriptPre;

std::string usdUiString = "(";
std::string usdSelector = "";
std::string otherUiString = "";
std::string otherSelector = "";

for (auto&& extension : SdfFileFormat::FindAllFileFormatExtensions()) {
// Put USD first
if (extension.rfind("usd", 0) == 0) {
if (!usdSelector.empty()) {
usdUiString += " ";
}
usdUiString += "*." + extension;
usdSelector += ";;*." + extension;
} else {
otherUiString += " *." + extension;
otherSelector += ";;*." + extension;
}
}
commandString += usdUiString + otherUiString + ")" + usdSelector + otherSelector
+ selectUSDFileScriptPost;
}
return commandString.c_str();
}

const char* clearAllReferencesConfirmScript = R"(
global proc string ClearAllUSDReferencesConfirm()
Expand Down Expand Up @@ -408,7 +445,11 @@ class AddUsdReferenceUndoableCommand : public Ufe::UndoableCommand
void redo() override
{
if (_prim.IsValid()) {
_sdfRef = SdfReference(_filePath);
if (TfStringEndsWith(_filePath, ".mtlx")) {
_sdfRef = SdfReference(_filePath, SdfPath("/MaterialX"));
} else {
_sdfRef = SdfReference(_filePath);
}
UsdReferences primRefs = _prim.GetReferences();
primRefs.AddReference(_sdfRef);
}
Expand Down Expand Up @@ -448,6 +489,123 @@ class ClearAllReferencesUndoableCommand : public Ufe::UndoableCommand
const std::string ClearAllReferencesUndoableCommand::commandName("Clear All References");
const MString ClearAllReferencesUndoableCommand::cancelRemoval("No");

#if PXR_VERSION >= 2108
class BindMaterialUndoableCommand : public Ufe::UndoableCommand
{
public:
static const std::string commandName;

BindMaterialUndoableCommand(const UsdPrim& prim, const SdfPath& materialPath)
: _stage(prim.GetStage())
, _primPath(prim.GetPath())
, _materialPath(materialPath)
{
}

void undo() override
{
if (!_stage || _primPath.IsEmpty() || _materialPath.IsEmpty()) {
return;
}

UsdPrim prim = _stage->GetPrimAtPath(_primPath);
if (prim.IsValid()) {
auto bindingAPI = UsdShadeMaterialBindingAPI(prim);
if (bindingAPI) {
bindingAPI.UnbindDirectBinding();
}
if (_appliedBindingAPI) {
prim.RemoveAPI<UsdShadeMaterialBindingAPI>();
}
}
}

void redo() override
{
if (!_stage || _primPath.IsEmpty() || _materialPath.IsEmpty()) {
return;
}

UsdPrim prim = _stage->GetPrimAtPath(_primPath);
UsdShadeMaterial material(_stage->GetPrimAtPath(_materialPath));
if (prim.IsValid() && material) {
UsdShadeMaterialBindingAPI bindingAPI;
if (prim.HasAPI<UsdShadeMaterialBindingAPI>()) {
bindingAPI = UsdShadeMaterialBindingAPI(prim);
} else {
bindingAPI = UsdShadeMaterialBindingAPI::Apply(prim);
_appliedBindingAPI = true;
}
bindingAPI.Bind(material);
}
}

private:
UsdStageWeakPtr _stage;
SdfPath _primPath;
SdfPath _materialPath;
bool _appliedBindingAPI = false;
};
const std::string BindMaterialUndoableCommand::commandName("Bind Material");

class UnbindMaterialUndoableCommand : public Ufe::UndoableCommand
{
public:
static const std::string commandName;

UnbindMaterialUndoableCommand(const UsdPrim& prim)
: _stage(prim.GetStage())
, _primPath(prim.GetPath())
{
}

void undo() override
{
if (!_stage || _primPath.IsEmpty() || _materialPath.IsEmpty()) {
return;
}

UsdPrim prim = _stage->GetPrimAtPath(_primPath);
UsdShadeMaterial material(_stage->GetPrimAtPath(_materialPath));
if (prim.IsValid() && material) {
// BindingAPI is still there since we did not remove it.
auto bindingAPI = UsdShadeMaterialBindingAPI(prim);
UsdShadeMaterial material(_stage->GetPrimAtPath(_materialPath));
if (bindingAPI && material) {
bindingAPI.Bind(material);
}
}
}

void redo() override
{
if (!_stage || _primPath.IsEmpty()) {
return;
}

UsdPrim prim = _stage->GetPrimAtPath(_primPath);
if (prim.IsValid()) {
auto bindingAPI = UsdShadeMaterialBindingAPI(prim);
if (bindingAPI) {
auto materialBinding = bindingAPI.GetDirectBinding();
_materialPath = materialBinding.GetMaterialPath();
if (!_materialPath.IsEmpty()) {
bindingAPI.UnbindDirectBinding();
// TODO: Can we remove the BindingAPI at this point?
// Not easy to know for sure.
}
}
}
}

private:
UsdStageWeakPtr _stage;
SdfPath _primPath;
SdfPath _materialPath;
};
const std::string UnbindMaterialUndoableCommand::commandName("Unbind Material");
#endif

std::vector<std::pair<const char* const, const char* const>>
_computeLoadAndUnloadItems(const UsdPrim& prim)
{
Expand Down Expand Up @@ -684,6 +842,67 @@ Ufe::ContextOps::Items UsdContextOps::getItems(const Ufe::ContextOps::ItemPath&
// Top level item - Add New Prim (for all context op types).
items.emplace_back(kUSDAddNewPrimItem, kUSDAddNewPrimLabel, Ufe::ContextItem::kHasChildren);

#if PXR_VERSION >= 2108
// Top level item - Bind/unbind existing materials
if (PXR_NS::UsdShadeMaterialBindingAPI::CanApply(fItem->prim())) {
// 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
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Any idea how to do this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Afraid not.

// 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.
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 (fItem->prim().HasAPI<UsdShadeMaterialBindingAPI>()) {
UsdShadeMaterialBindingAPI bindingAPI(fItem->prim());
// Show unbind menu item if there is a direct binding relationship:
auto directBinding = bindingAPI.GetDirectBinding();
if (directBinding.GetMaterial()) {
items.emplace_back(
UnbindMaterialUndoableCommand::commandName,
UnbindMaterialUndoableCommand::commandName);
}
}
#endif

if (!fIsAGatewayType) {
items.emplace_back(
AddUsdReferenceUndoableCommand::commandName,
Expand Down Expand Up @@ -772,9 +991,46 @@ Ufe::ContextOps::Items UsdContextOps::getItems(const Ufe::ContextOps::ItemPath&
}
#endif
} // If USD >= 20.08, submenus end here. Otherwise end of Root Setup

} // Add New Prim Item
} // Top-level items
}
#if PXR_VERSION >= 2108
else if (itemPath[0] == BindMaterialUndoableCommand::commandName) {
if (fItem) {
auto prim = fItem->prim();
if (prim) {
// Find materials in the global selection. Either directly selected or a direct
// child of the selection:
if (auto globalSn = Ufe::GlobalSelection::get()) {
// Use a set to keep names alphabetically ordered and unique.
std::set<std::string> foundMaterials;
for (auto&& selItem : *globalSn) {
UsdSceneItem::Ptr usdItem
= std::dynamic_pointer_cast<UsdSceneItem>(selItem);
if (!usdItem) {
continue;
}
UsdShadeMaterial material(usdItem->prim());
if (material) {
std::string currentPrimPath
= usdItem->prim().GetPath().GetAsString();
foundMaterials.insert(currentPrimPath);
}
for (auto&& usdChild : usdItem->prim().GetChildren()) {
UsdShadeMaterial material(usdChild);
if (material) {
std::string currentPrimPath = usdChild.GetPath().GetAsString();
foundMaterials.insert(currentPrimPath);
}
}
}
for (auto&& materialPath : foundMaterials) {
items.emplace_back(materialPath, materialPath);
}
}
}
}
}
#endif
} // Top-level items
return items;
}

Expand Down Expand Up @@ -842,7 +1098,7 @@ Ufe::UndoableCommand::Ptr UsdContextOps::doOpCmd(const ItemPath& itemPath)
return nullptr;
#endif
} else if (itemPath[0] == AddUsdReferenceUndoableCommand::commandName) {
MString fileRef = MGlobal::executeCommandStringResult(selectUSDFileScript);
MString fileRef = MGlobal::executeCommandStringResult(_selectUSDFileScript());

std::string path = UsdMayaUtil::convert(fileRef);
if (path.empty())
Expand Down Expand Up @@ -878,7 +1134,13 @@ Ufe::UndoableCommand::Ptr UsdContextOps::doOpCmd(const ItemPath& itemPath)
script, /* display = */ false, /* undoable = */ true);
}
#endif

#if PXR_VERSION >= 2108
else if (itemPath[0] == BindMaterialUndoableCommand::commandName) {
return std::make_shared<BindMaterialUndoableCommand>(fItem->prim(), SdfPath(itemPath[1]));
} else if (itemPath[0] == UnbindMaterialUndoableCommand::commandName) {
return std::make_shared<UnbindMaterialUndoableCommand>(fItem->prim());
}
#endif
return nullptr;
}

Expand Down
6 changes: 6 additions & 0 deletions lib/mayaUsd/ufe/UsdShaderNodeDef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ Ufe::ConstAttributeDefs getAttrs(const PXR_NS::SdrShaderNodeConstPtr& shaderNode
for (const PXR_NS::TfToken& name : names) {
PXR_NS::SdrShaderPropertyConstPtr property
= input ? shaderNodeDef->GetShaderInput(name) : shaderNodeDef->GetShaderOutput(name);
if (!property) {
// Cannot do much if the pointer is null. This can happen if the type_info for a
// class derived from SdrProperty is hidden inside a plugin library since
// SdrNode::GetShaderInput has to downcast a NdrProperty pointer.
continue;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have third-party shaders crashing here. They registered the input names, but did not register a SdrShaderProperty.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That would be a great code comment.

}
std::ostringstream defaultValue;
defaultValue << property->GetDefaultValue();
Ufe::Attribute::Type type = getUfeTypeForAttribute(property);
Expand Down
Loading