diff --git a/cmake/modules/FindUSD.cmake b/cmake/modules/FindUSD.cmake index 736616d6a2..121bc60774 100644 --- a/cmake/modules/FindUSD.cmake +++ b/cmake/modules/FindUSD.cmake @@ -124,6 +124,28 @@ if(USD_INCLUDE_DIR) endif() endif() +# See if MaterialX shaders with color4 inputs exist natively in Sdr: +# Not yet in a tagged USD version: https://github.com/PixarAnimationStudios/USD/pull/1894 +set(USD_HAS_COLOR4_SDR_SUPPORT FALSE CACHE INTERNAL "USD.Sdr.PropertyTypes.Color4") +if (USD_INCLUDE_DIR AND EXISTS "${USD_INCLUDE_DIR}/pxr/usd/sdr/shaderProperty.h") + file(STRINGS ${USD_INCLUDE_DIR}/pxr/usd/sdr/shaderProperty.h USD_HAS_API REGEX "Color4") + if(USD_HAS_API) + set(USD_HAS_COLOR4_SDR_SUPPORT TRUE CACHE INTERNAL "USD.Sdr.PropertyTypes.Color4") + message(STATUS "USD has new Sdr.PropertyTypes.Color4") + endif() +endif() + +# See if MaterialX shaders have full Metadata imported: +# Not yet in a tagged USD version: https://github.com/PixarAnimationStudios/USD/pull/1895 +set(USD_HAS_MX_METADATA_SUPPORT FALSE CACHE INTERNAL "USD.MaterialX.Metadata") +if (USD_LIBRARY_DIR AND EXISTS "${USD_LIBRARY_DIR}/${USD_LIB_PREFIX}usdMtlx${CMAKE_SHARED_LIBRARY_SUFFIX}") + file(STRINGS ${USD_LIBRARY_DIR}/${USD_LIB_PREFIX}usdMtlx${CMAKE_SHARED_LIBRARY_SUFFIX} USD_HAS_API REGEX "uisoftmin") + if(USD_HAS_API) + set(USD_HAS_MX_METADATA_SUPPORT TRUE CACHE INTERNAL "USD.MaterialX.Metadata") + message(STATUS "USD has MaterialX metadata support") + endif() +endif() + message(STATUS "USD include dir: ${USD_INCLUDE_DIR}") message(STATUS "USD library dir: ${USD_LIBRARY_DIR}") message(STATUS "USD version: ${USD_VERSION}") diff --git a/lib/mayaUsd/CMakeLists.txt b/lib/mayaUsd/CMakeLists.txt index 60e60c59f8..81dcf02dea 100644 --- a/lib/mayaUsd/CMakeLists.txt +++ b/lib/mayaUsd/CMakeLists.txt @@ -135,6 +135,14 @@ if (MAYA_HAS_GET_MEMBER_PATHS) ) endif() +message(STATUS "USD_HAS_COLOR4_SDR_SUPPORT is ${USD_HAS_COLOR4_SDR_SUPPORT}") +if (USD_HAS_COLOR4_SDR_SUPPORT) + target_compile_definitions(${PROJECT_NAME} + PRIVATE + USD_HAS_COLOR4_SDR_SUPPORT=1 + ) +endif() + message(STATUS "MAYA_HAS_DISPLAY_LAYER_API is ${MAYA_HAS_DISPLAY_LAYER_API}") if (MAYA_HAS_DISPLAY_LAYER_API) target_compile_definitions(${PROJECT_NAME} diff --git a/lib/mayaUsd/python/wrapUtil.cpp b/lib/mayaUsd/python/wrapUtil.cpp index 6edb011ade..e6cf610ef0 100644 --- a/lib/mayaUsd/python/wrapUtil.cpp +++ b/lib/mayaUsd/python/wrapUtil.cpp @@ -47,8 +47,9 @@ VtDictionary getDictionaryFromEncodedOptions(const std::string& textOptions) void wrapUtil() { - scope s(class_("Util")); - - def("IsAuthored", UsdMayaUtil::IsAuthored); - def("getDictionaryFromEncodedOptions", getDictionaryFromEncodedOptions); + scope s = class_("Util", no_init) + .def("IsAuthored", UsdMayaUtil::IsAuthored) + .def("prettifyName", &UsdMayaUtil::prettifyName) + .staticmethod("prettifyName") + .def("getDictionaryFromEncodedOptions", getDictionaryFromEncodedOptions); } diff --git a/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py b/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py index 71e1d5dd3e..bf44057075 100644 --- a/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py +++ b/lib/mayaUsd/resources/ae/usdschemabase/ae_template.py @@ -35,31 +35,12 @@ # We manually import all the classes which have a 'GetSchemaAttributeNames' # method so we have access to it and the 'pythonClass' method. -from pxr import Usd, UsdGeom, UsdLux, UsdRender, UsdRi, UsdShade, UsdSkel, UsdUI, UsdVol, Kind, Tf +from pxr import Usd, UsdGeom, UsdLux, UsdRender, UsdRi, UsdShade, UsdSkel, UsdUI, UsdVol, Kind, Tf, Sdr nameTxt = 'nameTxt' attrValueFld = 'attrValueFld' attrTypeFld = 'attrTypeFld' -def getPrettyName(name): - # Put a space in the name when preceded by a capital letter. - # Exceptions: Number followed by capital - # Multiple capital letters together - prettyName = str(name[0]) - nbChars = len(name) - for i in range(1, nbChars): - if name[i].isupper() and not name[i-1].isdigit(): - if (i < (nbChars-1)) and not name[i+1].isupper(): - prettyName += ' ' - prettyName += name[i] - elif name[i] == '_': - continue - else: - prettyName += name[i] - - # Make each word start with an uppercase. - return prettyName.title() - # Helper class to push/pop the Attribute Editor Template. This makes # sure that controls are aligned properly. class AEUITemplate: @@ -165,7 +146,7 @@ def onCreate(self, *args): for k in allMetadata: # All extra metadata is for display purposes only - it is not editable, but we # allow keyboard focus so you copy the value. - mdLabel = getPrettyName(k) if self.useNiceName else k + mdLabel = mayaUsdLib.Util.prettifyName(k) if self.useNiceName else k self.extraMetadata[k] = cmds.textFieldGrp(label=mdLabel, editable=False, enableKeyboardFocus=True) # Update all metadata values. @@ -262,7 +243,7 @@ def onCreate(self, *args): typeNameStr = str(typeName.scalarType) typeNameStr += ("[" + str(len(values)) + "]") if hasValue else "[]" - attrLabel = getPrettyName(self.attrName) if self.useNiceName else self.attrName + attrLabel = mayaUsdLib.Util.prettifyName(self.attrName) if self.useNiceName else self.attrName singleWidgetWidth = mel.eval('global int $gAttributeEditorTemplateSingleWidgetWidth; $gAttributeEditorTemplateSingleWidgetWidth += 0') with AEUITemplate(): # See comment in ConnectionsCustomControl below for why nc=5. @@ -324,7 +305,15 @@ def __init__(self, ufeItem, prim, attrName, useNiceName): def onCreate(self, *args): frontPath = self.path.popSegment() attr = self.prim.GetAttribute(self.attrName) - attrLabel = getPrettyName(self.attrName) if self.useNiceName else self.attrName + attrLabel = self.attrName + if self.useNiceName: + ufeItem = ufe.SceneItem(self.path) + ufeAttrS = ufe.Attributes.attributes(ufeItem) + ufeAttr = ufeAttrS.attribute(self.attrName) + attrLabel = str(ufeAttr.getMetadata("uiname")) + + if not attrLabel: + attrLabel = mayaUsdLib.Util.prettifyName(self.attrName) attrType = attr.GetMetadata('typeName') singleWidgetWidth = mel.eval('global int $gAttributeEditorTemplateSingleWidgetWidth; $gAttributeEditorTemplateSingleWidgetWidth += 0') @@ -497,7 +486,7 @@ def sectionNameFromSchema(self, schemaTypeName): schemaTypeName = schemaTypeName.replace(p, r, 1) break - schemaTypeName = getPrettyName(schemaTypeName) + schemaTypeName = mayaUsdLib.Util.prettifyName(schemaTypeName) # if the schema name ends with "api", replace it with # "API" and make sure to only replace the last occurence. @@ -507,6 +496,46 @@ def sectionNameFromSchema(self, schemaTypeName): return schemaTypeName + def createShaderAttributesSection(self): + """Use Sdr node information to populate the shader section""" + shader = UsdShade.Shader(self.prim) + nodeId = shader.GetIdAttr().Get() + if not nodeId: + return + nodeDef = Sdr.Registry().GetShaderNodeByIdentifier(nodeId) + if not nodeDef: + return + label = nodeDef.GetLabel() + if not label: + label = nodeDef.GetFamily() + # Hide all outputs: + for name in nodeDef.GetOutputNames(): + self.suppress(UsdShade.Utils.GetFullName(name, UsdShade.AttributeType.Output)) + with ufeAeTemplate.Layout(self, "Shader: " + mayaUsdLib.Util.prettifyName(label)): + pages = nodeDef.GetPages() + if len(pages) == 1 and not pages[0]: + pages = [] + if pages: + for page in pages: + collapse = False + pageLabel = page + if not page: + pageLabel = 'Unused Shader Attributes' + collapse = True + attrsToAdd = [] + for name in nodeDef.GetPropertyNamesForPage(page): + if nodeDef.GetInput(name): + name = UsdShade.Utils.GetFullName(name, UsdShade.AttributeType.Input) + attrsToAdd.append(name) + if attrsToAdd: + with ufeAeTemplate.Layout(self, mayaUsdLib.Util.prettifyName(pageLabel), collapse): + self.addControls(attrsToAdd) + else: + attrsToAdd = [] + for name in nodeDef.GetInputNames(): + attrsToAdd.append(UsdShade.Utils.GetFullName(name, UsdShade.AttributeType.Input)) + self.addControls(attrsToAdd) + def createTransformAttributesSection(self, sectionName, attrsToAdd): # Get the xformOp order and add those attributes (in order) # followed by the xformOp order attribute. @@ -638,9 +667,12 @@ def buildUI(self): sectionName = self.sectionNameFromSchema(schemaTypeName) if schemaType.pythonClass: attrsToAdd = schemaType.pythonClass.GetSchemaAttributeNames(False) - + if schemaTypeName == 'UsdShadeShader' and hasattr(ufe, "NodeDef"): + # Shader attributes are special, but we requires Ufe knowledge of NodeDef to + # show unauthored attributes. + self.createShaderAttributesSection() # We have a special case when building the Xformable section. - if schemaTypeName == 'UsdGeomXformable': + elif schemaTypeName == 'UsdGeomXformable': self.createTransformAttributesSection(sectionName, attrsToAdd) else: sectionsToCollapse = ['Curves', 'Point Based', 'Geometric Prim', 'Boundable', diff --git a/lib/mayaUsd/ufe/UsdAttribute.cpp b/lib/mayaUsd/ufe/UsdAttribute.cpp index dde02462de..4789f8ad8e 100644 --- a/lib/mayaUsd/ufe/UsdAttribute.cpp +++ b/lib/mayaUsd/ufe/UsdAttribute.cpp @@ -191,7 +191,11 @@ U getUsdAttributeVectorAsUfe( #else !attr.hasValue()) { #endif - vt = MayaUsd::ufe::vtValueFromString(attr.typeName(), attr.defaultValue()); + if (!attr.defaultValue().empty()) { + vt = MayaUsd::ufe::vtValueFromString(attr.typeName(), attr.defaultValue()); + } else { + return U(); + } } else if (!attr.get(vt, time) || !vt.IsHolding()) { return U(); } @@ -223,7 +227,11 @@ U getUsdAttributeColorAsUfe(const MayaUsd::ufe::UsdAttribute& attr, const PXR_NS #else !attr.hasValue()) { #endif - vt = MayaUsd::ufe::vtValueFromString(attr.typeName(), attr.defaultValue()); + if (!attr.defaultValue().empty()) { + vt = MayaUsd::ufe::vtValueFromString(attr.typeName(), attr.defaultValue()); + } else { + return U(); + } } else if (!attr.get(vt, time) || !vt.IsHolding()) { return U(); } @@ -257,7 +265,11 @@ U getUsdAttributeMatrixAsUfe( #else !attr.hasValue()) { #endif - vt = MayaUsd::ufe::vtValueFromString(attr.typeName(), attr.defaultValue()); + if (!attr.defaultValue().empty()) { + vt = MayaUsd::ufe::vtValueFromString(attr.typeName(), attr.defaultValue()); + } else { + return U(); + } } else if (!attr.get(vt, time) || !vt.IsHolding()) { return U(); } @@ -366,6 +378,12 @@ namespace ufe { UsdAttribute::UsdAttribute(const PXR_NS::UsdPrim& prim, const Ufe::AttributeDef::ConstPtr& attrDef) : fPrim(prim) , fAttrDef(attrDef) + , fUsdAttr(prim.GetAttribute(PXR_NS::UsdShadeUtils::GetFullName( + TfToken(attrDef->name()), + attrDef->ioType() == Ufe::AttributeDef::INPUT_ATTR ? PXR_NS::UsdShadeAttributeType::Input + : PXR_NS::UsdShadeAttributeType::Output + + ))) { PXR_NAMESPACE_USING_DIRECTIVE TF_VERIFY( @@ -583,16 +601,15 @@ Ufe::Value UsdAttribute::getMetadata(const std::string& key) const return Ufe::Value(ss.str()); } } - return Ufe::Value(); + } #ifdef UFE_V4_FEATURES_AVAILABLE #if (UFE_PREVIEW_VERSION_NUM >= 4008) - } else if (fAttrDef && fAttrDef->hasMetadata(key)) { + if (fAttrDef && fAttrDef->hasMetadata(key)) { return fAttrDef->getMetadata(key); + } #endif #endif - } else { - return Ufe::Value(); - } + return Ufe::Value(); } #ifdef UFE_V4_FEATURES_AVAILABLE @@ -805,8 +822,11 @@ UsdAttributeFilename::create(const UsdSceneItem::Ptr& item, const PXR_NS::UsdAtt std::string UsdAttributeFilename::get() const { + if (!hasValue()) + return std::string(); + PXR_NS::VtValue vt; - if (fUsdAttr.Get(&vt, getCurrentTime(sceneItem())) && vt.IsHolding()) { + if (UsdAttribute::get(vt, getCurrentTime(sceneItem())) && vt.IsHolding()) { SdfAssetPath path = vt.UncheckedGet(); return path.GetAssetPath(); } @@ -826,8 +846,8 @@ Ufe::UndoableCommand::Ptr UsdAttributeFilename::setCmd(const std::string& value) if (!TF_VERIFY(self, kErrorMsgInvalidType)) return nullptr; - std::string errMsg; - if (!MayaUsd::ufe::isAttributeEditAllowed(fUsdAttr, &errMsg)) { + const std::string errMsg = isEditAllowedMsg(); + if (!errMsg.empty()) { MGlobal::displayError(errMsg.c_str()); return nullptr; } diff --git a/lib/mayaUsd/ufe/UsdAttributes.cpp b/lib/mayaUsd/ufe/UsdAttributes.cpp index a29efff092..e4b1d34257 100644 --- a/lib/mayaUsd/ufe/UsdAttributes.cpp +++ b/lib/mayaUsd/ufe/UsdAttributes.cpp @@ -127,12 +127,11 @@ Ufe::Attribute::Type UsdAttributes::attributeType(const std::string& name) #ifdef UFE_V4_FEATURES_AVAILABLE #if (UFE_PREVIEW_VERSION_NUM >= 4008) Ufe::NodeDef::Ptr nodeDef = UsdAttributes::nodeDef(); - if (!nodeDef) { - return Ufe::Attribute::kInvalid; - } - Ufe::AttributeDef::ConstPtr attrDef = nameToAttrDef(tok, nodeDef); - if (attrDef) { - return attrDef->type(); + if (nodeDef) { + Ufe::AttributeDef::ConstPtr attrDef = nameToAttrDef(tok, nodeDef); + if (attrDef) { + return attrDef->type(); + } } #endif #endif @@ -167,16 +166,20 @@ Ufe::Attribute::Ptr UsdAttributes::attribute(const std::string& name) } #endif #endif + bool canCreateAttribute = usdAttr.IsValid(); if (usdAttr.IsValid()) { newAttrType = attributeType(name); } #ifdef UFE_V4_FEATURES_AVAILABLE #if (UFE_PREVIEW_VERSION_NUM >= 4008) - else if (!nodeDef) { - return nullptr; + if (nodeDef) { + canCreateAttribute = true; } #endif #endif + if (!canCreateAttribute) { + return nullptr; + } // Use a map of constructors to reduce the number of string comparisons. Since the naming // convention is extremely uniform, let's use a macro to simplify definition (and prevent @@ -189,10 +192,10 @@ Ufe::Attribute::Ptr UsdAttributes::attribute(const std::string& name) const PXR_NS::UsdPrim& prim, \ const Ufe::AttributeDef::ConstPtr& attrDef, \ const PXR_NS::UsdAttribute& usdAttr) { \ - if (usdAttr) { \ - return UsdAttribute##TYPE::create(si, usdAttr); \ - } else { \ + if (attrDef) { \ return UsdAttribute##TYPE::create(si, prim, attrDef); \ + } else { \ + return UsdAttribute##TYPE::create(si, usdAttr); \ } \ } \ } diff --git a/lib/mayaUsd/ufe/UsdShaderAttributeDef.cpp b/lib/mayaUsd/ufe/UsdShaderAttributeDef.cpp index e6ac8e8bd4..9577edf924 100644 --- a/lib/mayaUsd/ufe/UsdShaderAttributeDef.cpp +++ b/lib/mayaUsd/ufe/UsdShaderAttributeDef.cpp @@ -19,6 +19,8 @@ #include "Global.h" #include "Utils.h" +#include + #include #include #include @@ -69,6 +71,57 @@ Ufe::AttributeDef::IOType UsdShaderAttributeDef::ioType() const : Ufe::AttributeDef::INPUT_ATTR; } +namespace { +typedef std::unordered_map> + MetadataMap; +static const MetadataMap _metaMap = { + // Conversion map between known USD metadata and its MaterialX equivalent: + { "uiname", + [](const PXR_NS::SdrShaderProperty& p) { + return !p.GetLabel().IsEmpty() ? p.GetLabel().GetString() + : UsdMayaUtil::prettifyName(p.GetName().GetString()); + } }, + { "doc", + [](const PXR_NS::SdrShaderProperty& p) { + return !p.GetHelp().empty() ? p.GetHelp() : Ufe::Value(); + } }, + { "uifolder", + [](const PXR_NS::SdrShaderProperty& p) { + return !p.GetPage().IsEmpty() ? p.GetPage().GetString() : Ufe::Value(); + } }, + { "enum", + [](const PXR_NS::SdrShaderProperty& p) { + std::string r; + for (auto&& opt : p.GetOptions()) { + if (!r.empty()) { + r += ", "; + } + r += opt.first.GetString(); + } + return !r.empty() ? r : Ufe::Value(); + } }, + { "enumvalues", + [](const PXR_NS::SdrShaderProperty& p) { + std::string r; + for (auto&& opt : p.GetOptions()) { + if (opt.second.IsEmpty()) { + continue; + } + if (!r.empty()) { + r += ", "; + } + r += opt.second.GetString(); + } + return !r.empty() ? r : Ufe::Value(); + } }, + { "uisoftmax", // Maya has 0-100 sliders. In rendering, sliders are 0-1. + [](const PXR_NS::SdrShaderProperty&) { + return std::string { "1.0" }; // Will only be returned if the metadata does not exist. + } }, + // If Ufe decides to use another completely different convention, it can be added here: +}; +} // namespace + Ufe::Value UsdShaderAttributeDef::getMetadata(const std::string& key) const { TF_AXIOM(fShaderAttributeDef); @@ -77,8 +130,12 @@ Ufe::Value UsdShaderAttributeDef::getMetadata(const std::string& key) const if (it != metadata.cend()) { return Ufe::Value(it->second); } - // TODO: Adapt UI metadata information found in SdrShaderProperty to Ufe standards - // TODO: Fix Mtlx parser in USD to populate UI metadata in SdrShaderProperty + + MetadataMap::const_iterator itMapper = _metaMap.find(key); + if (itMapper != _metaMap.end()) { + return itMapper->second(*fShaderAttributeDef); + } + return {}; } @@ -87,7 +144,16 @@ bool UsdShaderAttributeDef::hasMetadata(const std::string& key) const TF_AXIOM(fShaderAttributeDef); const NdrTokenMap& metadata = fShaderAttributeDef->GetMetadata(); auto it = metadata.find(TfToken(key)); - return (it != metadata.cend()); + if (it != metadata.cend()) { + return true; + } + + MetadataMap::const_iterator itMapper = _metaMap.find(key); + if (itMapper != _metaMap.end() && !itMapper->second(*fShaderAttributeDef).empty()) { + return true; + } + + return false; } } // namespace ufe diff --git a/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp b/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp index c8b1844a6a..8198e7291c 100644 --- a/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp +++ b/lib/mayaUsd/ufe/UsdShaderNodeDef.cpp @@ -24,6 +24,8 @@ #include "Global.h" #include "Utils.h" +#include + #include #include #include @@ -224,6 +226,24 @@ Ufe::ConstAttributeDefs UsdShaderNodeDef::outputs() const return getAttrs(fShaderNodeDef); } +namespace { +typedef std::unordered_map> + MetadataMap; +static const MetadataMap _metaMap = { + // Conversion map between known USD metadata and its MaterialX equivalent: + { "uiname", + [](const PXR_NS::SdrShaderNode& n) { + return !n.GetLabel().IsEmpty() ? n.GetLabel().GetString() + : UsdMayaUtil::prettifyName(n.GetFamily().GetString()); + } }, + { "doc", + [](const PXR_NS::SdrShaderNode& n) { + return !n.GetHelp().empty() ? n.GetHelp() : Ufe::Value(); + } }, + // If Ufe decides to use another completely different convention, it can be added here: +}; +} // namespace + Ufe::Value UsdShaderNodeDef::getMetadata(const std::string& key) const { TF_AXIOM(fShaderNodeDef); @@ -232,8 +252,12 @@ Ufe::Value UsdShaderNodeDef::getMetadata(const std::string& key) const if (it != metadata.cend()) { return Ufe::Value(it->second); } - // TODO: Adapt UI metadata information found in SdrShaderNode to Ufe standards - // TODO: Fix Mtlx parser in USD to populate UI metadata in SdrShaderNode + + MetadataMap::const_iterator itMapper = _metaMap.find(key); + if (itMapper != _metaMap.end()) { + return itMapper->second(*fShaderNodeDef); + } + return {}; } @@ -242,7 +266,16 @@ bool UsdShaderNodeDef::hasMetadata(const std::string& key) const TF_AXIOM(fShaderNodeDef); const NdrTokenMap& metadata = fShaderNodeDef->GetMetadata(); auto it = metadata.find(TfToken(key)); - return it != metadata.cend(); + if (it != metadata.cend()) { + return true; + } + + MetadataMap::const_iterator itMapper = _metaMap.find(key); + if (itMapper != _metaMap.end() && !itMapper->second(*fShaderNodeDef).empty()) { + return true; + } + + return false; } Ufe::SceneItem::Ptr UsdShaderNodeDef::createNode( diff --git a/lib/mayaUsd/ufe/Utils.cpp b/lib/mayaUsd/ufe/Utils.cpp index 5da2b8977b..3f41b2c924 100644 --- a/lib/mayaUsd/ufe/Utils.cpp +++ b/lib/mayaUsd/ufe/Utils.cpp @@ -600,6 +600,9 @@ Ufe::Attribute::Type usdTypeToUfe(const PXR_NS::SdrShaderPropertyConstPtr& shade { PXR_NS::SdrPropertyTypes->String, PXR_NS::SdfValueTypeNames->String }, { PXR_NS::SdrPropertyTypes->Float, PXR_NS::SdfValueTypeNames->Float }, { PXR_NS::SdrPropertyTypes->Color, PXR_NS::SdfValueTypeNames->Color3f }, +#if defined(USD_HAS_COLOR4_SDR_SUPPORT) + { PXR_NS::SdrPropertyTypes->Color4, PXR_NS::SdfValueTypeNames->Color4f }, +#endif { PXR_NS::SdrPropertyTypes->Point, PXR_NS::SdfValueTypeNames->Point3f }, { PXR_NS::SdrPropertyTypes->Normal, PXR_NS::SdfValueTypeNames->Normal3f }, { PXR_NS::SdrPropertyTypes->Vector, PXR_NS::SdfValueTypeNames->Vector3f }, @@ -615,6 +618,12 @@ Ufe::Attribute::Type usdTypeToUfe(const PXR_NS::SdrShaderPropertyConstPtr& shade return usdTypeToUfe(PXR_NS::SdfValueTypeNames->Bool); } #endif + // There is no Matrix3d type in Sdr, so we need to infer it from Sdf until a fix similar + // to what was done to booleans is submitted to USD. This also means that there will be + // no default value for that type. + if (shaderProperty->GetType() == SdfValueTypeNames->Matrix3d.GetAsToken()) { + return usdTypeToUfe(PXR_NS::SdfValueTypeNames->Matrix3d); + } return usdTypeToUfe(PXR_NS::SdfValueTypeNames->Token); } } else { @@ -625,17 +634,26 @@ Ufe::Attribute::Type usdTypeToUfe(const PXR_NS::SdrShaderPropertyConstPtr& shade PXR_NS::SdfValueTypeName ufeTypeToUsd(const std::string& ufeType) { // Map the USD type into UFE type. - static const std::unordered_map sUfeTypeToUsd { - { Ufe::Attribute::kBool, PXR_NS::SdfValueTypeNames->Bool }, // bool - { Ufe::Attribute::kInt, PXR_NS::SdfValueTypeNames->Int }, // int32_t - { Ufe::Attribute::kFloat, PXR_NS::SdfValueTypeNames->Float }, // float - { Ufe::Attribute::kDouble, PXR_NS::SdfValueTypeNames->Double }, // double - { Ufe::Attribute::kString, PXR_NS::SdfValueTypeNames->String }, // std::string - { Ufe::Attribute::kEnumString, PXR_NS::SdfValueTypeNames->Token }, // TfToken - { Ufe::Attribute::kInt3, PXR_NS::SdfValueTypeNames->Int3 }, // GfVec3i - { Ufe::Attribute::kFloat3, PXR_NS::SdfValueTypeNames->Float3 }, // GfVec3f - { Ufe::Attribute::kDouble3, PXR_NS::SdfValueTypeNames->Double3 }, // GfVec3d - { Ufe::Attribute::kColorFloat3, PXR_NS::SdfValueTypeNames->Color3f }, // GfVec3f + static const std::unordered_map sUfeTypeToUsd + { + { Ufe::Attribute::kBool, PXR_NS::SdfValueTypeNames->Bool }, + { Ufe::Attribute::kInt, PXR_NS::SdfValueTypeNames->Int }, + { Ufe::Attribute::kFloat, PXR_NS::SdfValueTypeNames->Float }, + { Ufe::Attribute::kDouble, PXR_NS::SdfValueTypeNames->Double }, + { Ufe::Attribute::kString, PXR_NS::SdfValueTypeNames->String }, + { Ufe::Attribute::kEnumString, PXR_NS::SdfValueTypeNames->Token }, + { Ufe::Attribute::kInt3, PXR_NS::SdfValueTypeNames->Int3 }, + { Ufe::Attribute::kFloat3, PXR_NS::SdfValueTypeNames->Float3 }, + { Ufe::Attribute::kDouble3, PXR_NS::SdfValueTypeNames->Double3 }, + { Ufe::Attribute::kColorFloat3, PXR_NS::SdfValueTypeNames->Color3f }, +#if (UFE_PREVIEW_VERSION_NUM >= 4015) + { Ufe::Attribute::kFilename, PXR_NS::SdfValueTypeNames->Asset }, + { Ufe::Attribute::kFloat2, PXR_NS::SdfValueTypeNames->Float2 }, + { Ufe::Attribute::kFloat4, PXR_NS::SdfValueTypeNames->Float4 }, + { Ufe::Attribute::kColorFloat4, PXR_NS::SdfValueTypeNames->Color4f }, + { Ufe::Attribute::kMatrix3d, PXR_NS::SdfValueTypeNames->Matrix3d }, + { Ufe::Attribute::kMatrix4d, PXR_NS::SdfValueTypeNames->Matrix4d }, +#endif }; const auto iter = sUfeTypeToUsd.find(ufeType); @@ -669,6 +687,15 @@ PXR_NS::VtValue vtValueFromString(const std::string& typeName, const std::string std::stoi(tokens[1].c_str()), std::stoi(tokens[2].c_str())); } +#ifdef UFE_V4_FEATURES_AVAILABLE +#if (UFE_PREVIEW_VERSION_NUM >= 4015) + } else if (typeName == Ufe::Attribute::kFloat2) { + std::vector tokens = splitString(strValue, "()[], "); + if (TF_VERIFY(tokens.size() == 2, kInvalidValue, strValue.c_str(), typeName.c_str())) { + result = GfVec2f(std::stof(tokens[0].c_str()), std::stof(tokens[1].c_str())); + } +#endif +#endif } else if (typeName == Ufe::Attribute::kFloat3 || typeName == Ufe::Attribute::kColorFloat3) { std::vector tokens = splitString(strValue, "()[], "); if (TF_VERIFY(tokens.size() == 3, kInvalidValue, strValue.c_str(), typeName.c_str())) { @@ -677,6 +704,19 @@ PXR_NS::VtValue vtValueFromString(const std::string& typeName, const std::string std::stof(tokens[1].c_str()), std::stof(tokens[2].c_str())); } +#ifdef UFE_V4_FEATURES_AVAILABLE +#if (UFE_PREVIEW_VERSION_NUM >= 4015) + } else if (typeName == Ufe::Attribute::kFloat4 || typeName == Ufe::Attribute::kColorFloat4) { + std::vector tokens = splitString(strValue, "()[], "); + if (TF_VERIFY(tokens.size() == 4, kInvalidValue, strValue.c_str(), typeName.c_str())) { + result = GfVec4f( + std::stof(tokens[0].c_str()), + std::stof(tokens[1].c_str()), + std::stof(tokens[2].c_str()), + std::stof(tokens[3].c_str())); + } +#endif +#endif } else if (typeName == Ufe::Attribute::kDouble3) { std::vector tokens = splitString(strValue, "()[], "); if (TF_VERIFY(tokens.size() == 3, kInvalidValue, strValue.c_str(), typeName.c_str())) { @@ -710,6 +750,8 @@ PXR_NS::VtValue vtValueFromString(const std::string& typeName, const std::string } result = GfMatrix4d(m); } + } else if (typeName == Ufe::Attribute::kFilename) { + result = strValue; } #endif #endif diff --git a/lib/mayaUsd/utils/util.cpp b/lib/mayaUsd/utils/util.cpp index 46ceb37e03..60c9739a22 100644 --- a/lib/mayaUsd/utils/util.cpp +++ b/lib/mayaUsd/utils/util.cpp @@ -86,6 +86,8 @@ #include #include +#include + using namespace MAYAUSD_NS_DEF; PXR_NAMESPACE_USING_DIRECTIVE @@ -722,6 +724,32 @@ std::string UsdMayaUtil::SanitizeName(const std::string& name) return TfStringReplace(name, ":", "_"); } +std::string UsdMayaUtil::prettifyName(const std::string& name) +{ + std::string prettyName(1, std::toupper(name[0])); + size_t nbChars = name.size(); + bool capitalizeNext = false; + for (size_t i = 1; i < nbChars; ++i) { + unsigned char nextLetter = name[i]; + if (capitalizeNext) { + nextLetter = std::toupper(nextLetter); + capitalizeNext = false; + } + if (std::isupper(name[i]) && !std::isdigit(name[i - 1])) { + if ((i < (nbChars - 1)) && !std::isupper(name[i + 1])) { + prettyName += ' '; + } + prettyName += nextLetter; + } else if (name[i] == '_') { + prettyName += " "; + capitalizeNext = true; + } else { + prettyName += nextLetter; + } + } + return prettyName; +} + // This to allow various pipeline to sanitize the colorset name for output std::string UsdMayaUtil::SanitizeColorSetName(const std::string& name) { diff --git a/lib/mayaUsd/utils/util.h b/lib/mayaUsd/utils/util.h index 0090212399..047bcb97f9 100644 --- a/lib/mayaUsd/utils/util.h +++ b/lib/mayaUsd/utils/util.h @@ -287,6 +287,16 @@ std::string stripNamespaces(const std::string& nodeName, const int nsDepth = -1) MAYAUSD_CORE_PUBLIC std::string SanitizeName(const std::string& name); +/// Return a prettified name from camelCase or snake_case source. +/// +/// Put a space in the name when preceded by a capital letter. +/// Exceptions: Number followed by capital +/// Multiple capital letters together +/// Replace underscore by space and capitalize next letter +/// Always capitalize first letter +MAYAUSD_CORE_PUBLIC +std::string prettifyName(const std::string& name); + // This to allow various pipeline to sanitize the colorset name for output MAYAUSD_CORE_PUBLIC std::string SanitizeColorSetName(const std::string& name); diff --git a/test/lib/ufe/CMakeLists.txt b/test/lib/ufe/CMakeLists.txt index 5c62294bc9..d9fee8eb57 100644 --- a/test/lib/ufe/CMakeLists.txt +++ b/test/lib/ufe/CMakeLists.txt @@ -112,6 +112,8 @@ foreach(script ${TEST_SCRIPT_FILES}) "MAYA_PLUG_IN_PATH=${CMAKE_CURRENT_SOURCE_DIR}/ufeTestPlugins" "UFE_PREVIEW_VERSION_NUM=${UFE_PREVIEW_VERSION_NUM}" "LD_LIBRARY_PATH=${ADDITIONAL_LD_LIBRARY_PATH}" + "USD_HAS_COLOR4_SDR_SUPPORT=${USD_HAS_COLOR4_SDR_SUPPORT}" + "USD_HAS_MX_METADATA_SUPPORT=${USD_HAS_MX_METADATA_SUPPORT}" ) # Add a ctest label to these tests for easy filtering. diff --git a/test/lib/ufe/testAttribute.py b/test/lib/ufe/testAttribute.py index 3eedbe291b..7f7861a163 100644 --- a/test/lib/ufe/testAttribute.py +++ b/test/lib/ufe/testAttribute.py @@ -30,6 +30,7 @@ import mayaUsd from mayaUsd import ufe as mayaUsdUfe +from mayaUsd import lib as mayaUsdLib import ufe @@ -1535,7 +1536,7 @@ def createAndTestAttribute(self, materialItem, shaderDefName, shaderName, origVa shaderAttr.set(newValue) validation(self, shaderAttr.get(), newValue) - @unittest.skipIf(os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '4010', 'Test only available in UFE preview version 0.4.10 and greater') + @unittest.skipIf(os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '4015', 'Test only available in UFE preview version 0.4.15 and greater') @unittest.skipUnless(Usd.GetVersion() >= (0, 21, 8), 'Requires CanApplySchema from USD') def testCreateAttributeTypes(self): """Tests all shader attribute types""" @@ -1564,16 +1565,30 @@ def testCreateAttributeTypes(self): contextOps = ufe.ContextOps.contextOps(materialItem) floatAssert = lambda self, x, y : self.assertAlmostEqual(x, y) - color3Assert = lambda self, x, y : testUtils.assertVectorAlmostEqual(self, x.color, y.color) - vector3Assert = lambda self, x, y : testUtils.assertVectorAlmostEqual(self, x.vector, y.vector) + colorAssert = lambda self, x, y : testUtils.assertVectorAlmostEqual(self, x.color, y.color) + vectorAssert = lambda self, x, y : testUtils.assertVectorAlmostEqual(self, x.vector, y.vector) + matrixAssert = lambda self, x, y : testUtils.assertMatrixAlmostEqual(self, x.matrix, y.matrix) normalAssert = lambda self, x, y : self.assertEqual(x, y) origFloat = 0.0 newFloat = 0.6 origColor3 = ufe.Color3f(0.0, 0.0, 0.0) newColor3 = ufe.Color3f(0.2, 0.4, 0.6) + origColor4 = ufe.Color4f(0.0, 0.0, 0.0, 0.0) + newColor4 = ufe.Color4f(0.2, 0.4, 0.6, 0.8) + origVector2 = ufe.Vector2f(0.0, 0.0) + newVector2 = ufe.Vector2f(0.2, 0.4) origVector3 = ufe.Vector3f(0.0, 0.0, 0.0) newVector3 = ufe.Vector3f(0.2, 0.4, 0.6) + origVector4 = ufe.Vector4f(0.0, 0.0, 0.0, 0.0) + newVector4 = ufe.Vector4f(0.2, 0.4, 0.6, 0.8) + # Default Matrix33 should be identity, but USD does not store a default value for that type. + # Requires same fix as Boolean in pxr/usd/usdMtlx/parser.cpp + # See: https://github.com/PixarAnimationStudios/USD/pull/1789 + origMatrix3 = ufe.Matrix3d([[0, 0, 0], [0, 0, 0], [0, 0, 0]]) + newMatrix3 = ufe.Matrix3d([[2, 4, 6], [7, 5, 3], [1, 2, 3]]) + origMatrix4 = ufe.Matrix4d([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) + newMatrix4 = ufe.Matrix4d([[1, 2, 1, 1], [0, 1, 0, 1], [2, 3, 4, 1], [1, 1, 1, 1]]) origBoolean = False newBoolean = True origInteger = 0 @@ -1581,14 +1596,66 @@ def testCreateAttributeTypes(self): origString = "" newString = "test" - # TODO: implement color4, vector2, vector4, matrix33, matrix44, filename - self.createAndTestAttribute(materialItem, "ND_constant_float", "ConstantFloat", origFloat, newFloat, floatAssert) - self.createAndTestAttribute(materialItem, "ND_constant_color3", "ConstantColor3_", origColor3, newColor3, color3Assert) - self.createAndTestAttribute(materialItem, "ND_constant_vector3", "ConstantVector3_", origVector3, newVector3, vector3Assert) + self.createAndTestAttribute(materialItem, "ND_constant_color3", "ConstantColor3_", origColor3, newColor3, colorAssert) + if os.getenv('USD_HAS_COLOR4_SDR_SUPPORT', 'NOT-FOUND') in ('1', "TRUE"): + self.createAndTestAttribute(materialItem, "ND_constant_color4", "ConstantColor4_", origColor4, newColor4, colorAssert) + self.createAndTestAttribute(materialItem, "ND_constant_vector2", "ConstantVector2_", origVector2, newVector2, vectorAssert) + self.createAndTestAttribute(materialItem, "ND_constant_vector3", "ConstantVector3_", origVector3, newVector3, vectorAssert) + self.createAndTestAttribute(materialItem, "ND_constant_vector4", "ConstantVector4_", origVector4, newVector4, vectorAssert) + self.createAndTestAttribute(materialItem, "ND_constant_matrix33", "ConstantMatrix3d_", origMatrix3, newMatrix3, matrixAssert) + self.createAndTestAttribute(materialItem, "ND_constant_matrix44", "ConstantMatrix4d_", origMatrix4, newMatrix4, matrixAssert) self.createAndTestAttribute(materialItem, "ND_constant_boolean", "ConstantBoolean", origBoolean, newBoolean, normalAssert) self.createAndTestAttribute(materialItem, "ND_constant_integer", "ConstantInteger", origInteger, newInteger, normalAssert) self.createAndTestAttribute(materialItem, "ND_constant_string", "ConstantString", origString, newString, normalAssert) + self.createAndTestAttribute(materialItem, "ND_constant_filename", "ConstantFilename", origString, newString, normalAssert) + + @unittest.skipIf(os.getenv('USD_HAS_MX_METADATA_SUPPORT', 'NOT-FOUND') not in ('1', "TRUE"), 'Test only available if USD can read MaterialX metadata') + def testMaterialXMetadata(self): + """Tests all known metadata""" + cmds.file(new=True, force=True) + + # Create a proxy shape with empty stage to start with. + import mayaUsd_createStageWithNewLayer + proxyShape = mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + + # Create a ContextOps interface for the proxy shape. + proxyPathSegment = mayaUtils.createUfePathSegment(proxyShape) + proxyShapePath = ufe.Path([proxyPathSegment]) + proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath) + contextOps = ufe.ContextOps.contextOps(proxyShapeItem) + + cmd = contextOps.doOpCmd(['Add New Prim', 'Material']) + ufeCmd.execute(cmd) + + rootHier = ufe.Hierarchy.hierarchy(proxyShapeItem) + self.assertTrue(rootHier.hasChildren()) + self.assertEqual(len(rootHier.children()), 1) + + materialItem = rootHier.children()[0] + + surfDef = ufe.NodeDef.definition(materialItem.runTimeId(), "ND_standard_surface_surfaceshader") + self.assertEqual(str(surfDef.getMetadata("uiname")), "standard_surface") + self.assertEqual(str(surfDef.getMetadata("doc")), "Autodesk standard surface shader") + + cmd = surfDef.createNodeCmd(materialItem, ufe.PathComponent("MySurf")) + ufeCmd.execute(cmd) + shaderItem = cmd.insertedChild + + shaderAttrs = ufe.Attributes.attributes(shaderItem) + expected = ( + ("uimin", "base", "0.0"), + ("uimax", "base", "1.0"), + ("uisoftmin", "transmission_extra_roughness", "0.0"), + ("uisoftmax", "coat_IOR", "3.0"), + ("uiname", "base_color", "Base Color"), + ("uifolder", "transmission_color", "Transmission"), + ("defaultgeomprop", "coat_normal", "Nworld"), + ) + + for metaName, attrName, metaValue in expected: + attr = shaderAttrs.attribute("inputs:" + attrName) + self.assertEqual(str(attr.getMetadata(metaName)), metaValue) @unittest.skipIf(os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '4010', 'Test only available in UFE preview version 0.4.10 and greater') @unittest.skipUnless(Usd.GetVersion() >= (0, 21, 8), 'Requires CanApplySchema from USD') @@ -1608,5 +1675,17 @@ def testCreateUsdPreviewSurfaceAttribute(self): shaderAttr.set(0.8) self.assertAlmostEqual(shaderAttr.get(), 0.8) + 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("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") + if __name__ == '__main__': unittest.main(verbosity=2)