Skip to content

Commit

Permalink
MAYA-128779 - Improvements for surface shader querying and handling
Browse files Browse the repository at this point in the history
- Utility function UsdMayaUtil::GetSurfaceShaderNodeDefs() for getting vetted surfaces as well as those with "shader/surface" role (adds afew arnold ones, potentially more)
- UsdShaderNodeDef::definitions can now be queried for surface definitions only in order to access them through UFE
- UsdShaderNodeDef::createNodeCmd will implicitly create a material if given a valid material scope
- UsdUndoAddNewMaterialCommand will only attempt to connect surface nodes to material output
- UI specific string requirements have been pushed to UsdMayaUtil::prettifyName for consistency
  • Loading branch information
gz-adsk committed Apr 5, 2023
1 parent 3dcf73e commit 5a2e1dc
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 13 deletions.
26 changes: 22 additions & 4 deletions lib/mayaUsd/ufe/UsdShaderNodeDef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#if (UFE_PREVIEW_VERSION_NUM >= 4010)
#include <mayaUsd/ufe/UsdShaderAttributeDef.h>
#include <mayaUsd/ufe/UsdUndoCreateFromNodeDefCommand.h>
#include <mayaUsd/ufe/UsdUndoMaterialCommands.h>
#endif

#include "Global.h"
Expand All @@ -45,6 +46,7 @@ namespace ufe {
PXR_NAMESPACE_USING_DIRECTIVE

constexpr char UsdShaderNodeDef::kNodeDefCategoryShader[];
constexpr char UsdShaderNodeDef::kNodeDefCategorySurface[];

template <Ufe::AttributeDef::IOType IOTYPE>
Ufe::ConstAttributeDefs getAttrs(const SdrShaderNodeConstPtr& shaderNodeDef)
Expand Down Expand Up @@ -361,6 +363,12 @@ Ufe::InsertChildCommand::Ptr UsdShaderNodeDef::createNodeCmd(
TF_AXIOM(fShaderNodeDef);
UsdSceneItem::Ptr parentItem = std::dynamic_pointer_cast<UsdSceneItem>(parent);
if (parentItem) {
if (parentItem->nodeType() == "Scope"
&& parentItem->nodeName()
== UsdUndoAssignNewMaterialCommand::resolvedMaterialScopeName()) {
return UsdUndoAddNewMaterialCommand::create(
parentItem, fShaderNodeDef->GetIdentifier());
}
return UsdUndoCreateFromNodeDefCommand::create(
fShaderNodeDef, parentItem, UsdMayaUtil::SanitizeName(name.string()));
}
Expand All @@ -379,11 +387,21 @@ UsdShaderNodeDef::Ptr UsdShaderNodeDef::create(const SdrShaderNodeConstPtr& shad

Ufe::NodeDefs UsdShaderNodeDef::definitions(const std::string& category)
{
static std::unordered_set<std::string> validCategories = {
Ufe::NodeDefHandler::kNodeDefCategoryAll, kNodeDefCategoryShader, kNodeDefCategorySurface
};

Ufe::NodeDefs result;
if (category == std::string(Ufe::NodeDefHandler::kNodeDefCategoryAll)
|| category == std::string(kNodeDefCategoryShader)) {
SdrRegistry& registry = SdrRegistry::GetInstance();
SdrShaderNodePtrVec shaderNodeDefs = registry.GetShaderNodesByFamily();
if (validCategories.count(category)) {
SdrShaderNodePtrVec shaderNodeDefs;

if (category == kNodeDefCategorySurface) {
shaderNodeDefs = UsdMayaUtil::GetSurfaceShaderNodeDefs();
} else {
SdrRegistry& registry = SdrRegistry::GetInstance();
shaderNodeDefs = registry.GetShaderNodesByFamily();
}

result.reserve(shaderNodeDefs.size());
for (const SdrShaderNodeConstPtr& shaderNodeDef : shaderNodeDefs) {
result.emplace_back(UsdShaderNodeDef::create(shaderNodeDef));
Expand Down
1 change: 1 addition & 0 deletions lib/mayaUsd/ufe/UsdShaderNodeDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class MAYAUSD_CORE_PUBLIC UsdShaderNodeDef : public Ufe::NodeDef
typedef std::shared_ptr<UsdShaderNodeDef> Ptr;

static constexpr char kNodeDefCategoryShader[] = "Shader";
static constexpr char kNodeDefCategorySurface[] = "Surface";

UsdShaderNodeDef(const PXR_NS::SdrShaderNodeConstPtr& shaderNodeDef);
~UsdShaderNodeDef();
Expand Down
14 changes: 9 additions & 5 deletions lib/mayaUsd/ufe/UsdUndoMaterialCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <mayaUsd/fileio/jobs/jobArgs.h>
#include <mayaUsd/ufe/Utils.h>
#include <mayaUsd/undo/UsdUndoBlock.h>
#include <mayaUsd/utils/util.h>

#include <pxr/usd/sdr/registry.h>
#include <pxr/usd/sdr/shaderProperty.h>
Expand Down Expand Up @@ -606,12 +607,15 @@ void UsdUndoAddNewMaterialCommand::execute()
}

//
// Connect the Shader to the material:
// Connect the Shader to the material, only for surfaces:
//
if (!connectShaderToMaterial(
_createShaderCmd->insertedChild(), _createMaterialCmd->newPrim(), _nodeId)) {
markAsFailed();
return;
auto surfaces = UsdMayaUtil::GetSurfaceShaderNodeDefs();
if (std::find(surfaces.begin(), surfaces.end(), shaderNodeDef) != surfaces.end()) {
if (!connectShaderToMaterial(
_createShaderCmd->insertedChild(), _createMaterialCmd->newPrim(), _nodeId)) {
markAsFailed();
return;
}
}
}

Expand Down
54 changes: 54 additions & 0 deletions lib/mayaUsd/utils/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@

#include <boost/functional/hash.hpp>

#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
Expand All @@ -81,6 +82,7 @@
#include <pxr/base/vt/value.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/sdf/tokens.h>
#include <pxr/usd/sdr/registry.h>
#include <pxr/usd/usdGeom/camera.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/metrics.h>
Expand Down Expand Up @@ -749,6 +751,27 @@ std::string UsdMayaUtil::prettifyName(const std::string& name)
prettyName += nextLetter;
}
}
// Manual substitutions for custom capitalisations. Will be searched as case-insensitive.
static const std::unordered_map<std::string, std::string> subs = {
{ "usd", "USD" },
{ "mtlx", "MaterialX" },
{ "gltf pbr", "glTF PBR" },
};

static const auto subRegexes = []() {
std::vector<std::pair<std::regex, std::string>> regexes;
regexes.reserve(subs.size());
for (auto&& kv : subs) {
regexes.emplace_back(
std::regex { "\\b" + kv.first + "\\b", std::regex_constants::icase }, kv.second);
}
return regexes;
}();

for (auto&& re : subRegexes) {
prettyName = regex_replace(prettyName, re.first, re.second);
}

return prettyName;
}

Expand Down Expand Up @@ -2554,3 +2577,34 @@ void UsdMayaUtil::AddMayaExtents(GfBBox3d& bbox, const UsdPrim& root, const UsdT
}
}
}

SdrShaderNodePtrVec UsdMayaUtil::GetSurfaceShaderNodeDefs()
{
// TODO: Replace hard-coded materials with dynamically generated list.
static const std::set<TfToken> vettedSurfaces = { TfToken("ND_standard_surface_surfaceshader"),
TfToken("ND_gltf_pbr_surfaceshader"),
TfToken("ND_UsdPreviewSurface_surfaceshader"),
TfToken("UsdPreviewSurface") };

SdrShaderNodePtrVec surfaceShaderNodeDefs;

auto& registry = SdrRegistry::GetInstance();
for (auto&& id : vettedSurfaces) {
SdrShaderNodeConstPtr shaderNodeDef = registry.GetShaderNodeByIdentifier(id);
if (shaderNodeDef) {
surfaceShaderNodeDefs.emplace_back(shaderNodeDef);
}
}

for (auto&& sdrNode : registry.GetShaderNodesByFamily()) {
// Any shader that has the "shader/surface" role will
// be exposed in the material creation menus. This
// includes nodes from Arnold, but also VRay, PRMan,
// if they use the same string to tag surface nodes.
if (sdrNode->GetRole() == "shader/surface") {
surfaceShaderNodeDefs.emplace_back(sdrNode);
}
}

return surfaceShaderNodeDefs;
}
5 changes: 5 additions & 0 deletions lib/mayaUsd/utils/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <pxr/base/vt/value.h>
#include <pxr/pxr.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/sdr/declare.h>
#include <pxr/usd/usd/attribute.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usd/timeCode.h>
Expand Down Expand Up @@ -695,6 +696,10 @@ void AddMayaExtents(
const PXR_NS::UsdPrim& root,
const PXR_NS::UsdTimeCode time);

/// Access to materials associated with available renderers
MAYAUSD_CORE_PUBLIC
SdrShaderNodePtrVec GetSurfaceShaderNodeDefs();

} // namespace UsdMayaUtil

PXR_NAMESPACE_CLOSE_SCOPE
Expand Down
28 changes: 25 additions & 3 deletions plugin/adsk/plugin/adskMaterialCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@

#include <mayaUsd/ufe/Global.h>
#include <mayaUsd/ufe/UsdSceneItem.h>
#if (UFE_PREVIEW_VERSION_NUM >= 4001)
#include <mayaUsd/ufe/UsdShaderNodeDef.h>
#endif
#include <mayaUsd/ufe/Utils.h>
#include <mayaUsd/utils/query.h>
#include <mayaUsd/utils/util.h>
Expand Down Expand Up @@ -60,6 +63,7 @@ MStatus ADSKMayaUSDGetMaterialsForRenderersCommand::parseArgs(const MArgList& ar
return MS::kSuccess;
}

#if (UFE_PREVIEW_VERSION_NUM < 4001)
void ADSKMayaUSDGetMaterialsForRenderersCommand::appendMaterialXMaterials() const
{
// TODO: Replace hard-coded materials with dynamically generated list.
Expand Down Expand Up @@ -101,6 +105,7 @@ void ADSKMayaUSDGetMaterialsForRenderersCommand::appendUsdMaterials() const
const MString identifier = "UsdPreviewSurface";
appendToResult("USD/" + label + "|" + identifier);
}
#endif

// main MPxCommand execution point
MStatus ADSKMayaUSDGetMaterialsForRenderersCommand::doIt(const MArgList& argList)
Expand All @@ -111,12 +116,29 @@ MStatus ADSKMayaUSDGetMaterialsForRenderersCommand::doIt(const MArgList& argList
if (!status)
return status;

// TODO: The list of returned materials is currently hard-coded and only for select,
// known renderers. We should populate the material lists dynamically based on what the
// installed renderers report as supported materials.
// TODO: The list of returned materials is currently hard-coded and only for select,
// known renderers. We should populate the material lists dynamically based on what the
// installed renderers report as supported materials.

#if (UFE_PREVIEW_VERSION_NUM >= 4001)
const auto shaderNodeDefs = UsdMayaUtil::GetSurfaceShaderNodeDefs();
for (const auto& nodeDef : shaderNodeDefs) {
// To make use of ufe classifications
auto ufeNodeDef = ufe::UsdShaderNodeDef::create(nodeDef);
auto familyName = ufeNodeDef->classification(0);
auto sourceType = ufeNodeDef->classification(ufeNodeDef->nbClassifications() - 1);
appendToResult(MString(TfStringPrintf(
"%s/%s|%s",
UsdMayaUtil::prettifyName(sourceType).c_str(),
UsdMayaUtil::prettifyName(familyName).c_str(),
nodeDef->GetIdentifier().GetText())
.c_str()));
}
#else
appendUsdMaterials();
appendArnoldMaterials();
appendMaterialXMaterials();
#endif

return MS::kSuccess;
}
Expand Down
2 changes: 2 additions & 0 deletions plugin/adsk/plugin/adskMaterialCommands.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ class MAYAUSD_PLUGIN_PUBLIC ADSKMayaUSDGetMaterialsForRenderersCommand : public
private:
MStatus parseArgs(const MArgList& argList);

#if (UFE_PREVIEW_VERSION_NUM < 4001)
void appendMaterialXMaterials() const;
void appendArnoldMaterials() const;
void appendUsdMaterials() const;
#endif
};

//! \brief Returns an array of materials in the same stage as the object passed in via argument.
Expand Down
5 changes: 4 additions & 1 deletion test/lib/ufe/testAttribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -1925,14 +1925,17 @@ def testNamePrettification(self):
'''Test the name prettification routine.'''
self.assertEqual(mayaUsdLib.Util.prettifyName("standard_surface"), "Standard Surface")
self.assertEqual(mayaUsdLib.Util.prettifyName("standardSurface"), "Standard Surface")
self.assertEqual(mayaUsdLib.Util.prettifyName("UsdPreviewSurface"), "Usd Preview Surface")
self.assertEqual(mayaUsdLib.Util.prettifyName("USDPreviewSurface"), "USD Preview Surface")
self.assertEqual(mayaUsdLib.Util.prettifyName("xformOp:rotateXYZ"), "Xform Op Rotate XYZ")
self.assertEqual(mayaUsdLib.Util.prettifyName("ior"), "Ior")
self.assertEqual(mayaUsdLib.Util.prettifyName("IOR"), "IOR")
self.assertEqual(mayaUsdLib.Util.prettifyName("specular_IOR"), "Specular IOR")
# This is as expected as we do not insert space on digit<->alpha transitions:
self.assertEqual(mayaUsdLib.Util.prettifyName("Dx11Shader"), "Dx11Shader")
# Explicit substitutions
self.assertEqual(mayaUsdLib.Util.prettifyName("UsdPreviewSurface"), "USD Preview Surface")
self.assertEqual(mayaUsdLib.Util.prettifyName("mtlx"), "MaterialX")
self.assertEqual(mayaUsdLib.Util.prettifyName("gltf_pbr"), "glTF PBR")

@unittest.skipIf(os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '4037', 'Test only available in UFE preview version 0.4.37 and greater')
def testAttributeMetadataChanged(self):
Expand Down
70 changes: 70 additions & 0 deletions test/lib/ufe/testShaderNodeDef.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ def getNodeDefHandler(self):
nodeDefHandler = ufe.RunTimeMgr.instance().nodeDefHandler(rid)
self.assertIsNotNone(nodeDefHandler)
return nodeDefHandler

def getConnectionHandler(self):
rid = ufe.RunTimeMgr.instance().getId('USD')
connectionHandler = ufe.RunTimeMgr.instance().connectionHandler(rid)
self.assertIsNotNone(connectionHandler)
return connectionHandler

@unittest.skipIf(os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '4001', 'nodeDefHandler is only available in UFE preview version 0.4.1 and greater')
def testDefinitions(self):
Expand Down Expand Up @@ -213,5 +219,69 @@ def testClassificationsAndMetadata(self):
output = nodeDef.output("out")
self.assertEqual(output.getMetadata("__SDR__defaultinput"), ufe.Value("in1"))

@unittest.skipIf(os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '4001', 'nodeDefHandler is only available in UFE preview version 0.4.1 and greater')
def testNodeCreation(self):
import mayaUsd_createStageWithNewLayer
mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
proxyShapePathStr = '|stage1|stageShape1'
stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage()
nodeDefHandler = self.getNodeDefHandler()
connectionHandler = self.getConnectionHandler()

# Create a simple hierarchy.
scopePrim = stage.DefinePrim('/mtl', 'Scope')
scopePathStr = proxyShapePathStr + ",/mtl"
self.assertIsNotNone(scopePrim)
materialPrim = stage.DefinePrim('/mtl/Material1', 'Material')
materialPathStr = scopePathStr + "/Material1"
self.assertIsNotNone(materialPrim)
nodeGraphPrim = stage.DefinePrim('/mtl/Material1/NodeGraph1', 'NodeGraph')
nodeGraphPathStr = materialPathStr + "/NodeGraph1"
self.assertIsNotNone(nodeGraphPrim)

# Construct a surface shader node inside the existing material.
type = "ND_UsdPreviewSurface_surfaceshader"
nodeDef = nodeDefHandler.definition(type)
self.assertIsNotNone(nodeDef)
materialSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(materialPathStr))
self.assertIsNotNone(materialSceneItem)
command = nodeDef.createNodeCmd(materialSceneItem, ufe.PathComponent("UsdPreviewSurface"))
self.assertIsNotNone(command)
command.execute()
self.assertIsNotNone(command.insertedChild)
# Verify it is created in the right place and has no connections
self.assertEqual(ufe.PathString.string(command.insertedChild.path()), materialPathStr + "/UsdPreviewSurface1")
connections = connectionHandler.sourceConnections(materialSceneItem)
self.assertIsNotNone(connections)
self.assertEqual(len(connections.allConnections()), 0)

# Construct a surface shader node at the mtl scope
scopeSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(scopePathStr))
self.assertIsNotNone(scopeSceneItem)
command = nodeDef.createNodeCmd(scopeSceneItem, ufe.PathComponent("UsdPreviewSurface"))
self.assertIsNotNone(command)
command.execute()
self.assertIsNotNone(command.insertedChild)
# Verify that a new material is created, matching the shader name, and the connection set
self.assertEqual(ufe.PathString.string(command.insertedChild.path()), scopePathStr + "/UsdPreviewSurface1/UsdPreviewSurface1")
newMaterialSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(scopePathStr + "/UsdPreviewSurface1"))
connections = connectionHandler.sourceConnections(newMaterialSceneItem)
self.assertIsNotNone(connections)
self.assertEqual(len(connections.allConnections()), 1)

# Construct a non surface shader node at the mtl scope
type = "ND_image_color3"
nodeDef = nodeDefHandler.definition(type)
self.assertIsNotNone(nodeDef)
scopeSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(scopePathStr))
self.assertIsNotNone(scopeSceneItem)
command = nodeDef.createNodeCmd(scopeSceneItem, ufe.PathComponent("image"))
self.assertIsNotNone(command)
command.execute()
self.assertIsNotNone(command.insertedChild)
# Verify that a new material is created, but without connections this time as it is not a surface
self.assertEqual(ufe.PathString.string(command.insertedChild.path()), scopePathStr + "/image1/image1")
newMaterialSceneItem = ufe.Hierarchy.createItem(ufe.PathString.path(scopePathStr + "/image1"))
connections = connectionHandler.sourceConnections(newMaterialSceneItem)
self.assertIsNotNone(connections)
self.assertEqual(len(connections.allConnections()), 0)

0 comments on commit 5a2e1dc

Please sign in to comment.