From 10d510b14b1ec021214051dbf99f0b4aba100f33 Mon Sep 17 00:00:00 2001 From: shriramiyer Date: Fri, 24 Mar 2017 11:00:37 -0700 Subject: [PATCH] Connectability of shading attributes Added connectability as token valued metadata on attributes. In certain shading models, there is a need to distinguish between the inputs can vary over a surface and those that must be uniform. This is accomplished in USD-shade by limiting the connectability of the input, by setting the "connectability" metadata on the associated attribute. Connectability of an Input can be set to "full" or "interfaceOnly". * "full "implies that the Input can be connected to any other Input or Output. * "interfaceOnly" implies that the Input can only be connected to a NodeGraph Input (which represents an interface override, not a render-time dataflow connection), or another Input whose connectability is also "interfaceOnly". When connectability isn't authored on a shading node, it defaults to "full" on a Shader and to "interfaceOnly" on a NodeGraph. When the prim type is unknown, the fallback value (which is set in the schema registry) is "full". Added UsdShadeConnectableAPI::CanConnect to help clients determine if a input or output *can* be connected to a given shading attribute. UsdShadeConnectableAPI::ConnectToSource() no longer validates the the attempted connection. It simply goes ahead and tries to author it. Clients are responsible for calling CanConnect() themselves when they care about validation. Unit test update is coming soon. (Internal change: 1727857) --- pxr/usd/lib/usdShade/connectableAPI.cpp | 147 +++++++++++++++----- pxr/usd/lib/usdShade/connectableAPI.h | 25 ++++ pxr/usd/lib/usdShade/input.cpp | 45 ++++++ pxr/usd/lib/usdShade/input.h | 42 ++++++ pxr/usd/lib/usdShade/plugInfo.json | 7 +- pxr/usd/lib/usdShade/schema.usda | 15 ++ pxr/usd/lib/usdShade/tokens.h | 4 + pxr/usd/lib/usdShade/wrapConnectableAPI.cpp | 20 ++- pxr/usd/lib/usdShade/wrapInput.cpp | 4 + 9 files changed, 267 insertions(+), 42 deletions(-) diff --git a/pxr/usd/lib/usdShade/connectableAPI.cpp b/pxr/usd/lib/usdShade/connectableAPI.cpp index a375baee83..2ffbff8b05 100644 --- a/pxr/usd/lib/usdShade/connectableAPI.cpp +++ b/pxr/usd/lib/usdShade/connectableAPI.cpp @@ -141,6 +141,116 @@ UsdShadeConnectableAPI::_IsCompatible(const UsdPrim &prim) const return IsShader() || IsNodeGraph(); } +static bool +_CanConnectOutputToSource(const UsdShadeOutput &output, + const UsdAttribute &source, + std::string *reason) +{ + if (not output.IsDefined()) { + if (reason) { + *reason = TfStringPrintf("Invalid output"); + } + return false; + } + + // Only outputs on node-graphs are connectable. + if (not UsdShadeConnectableAPI(output.GetPrim()).IsNodeGraph()) { + if (reason) { + *reason = "Output does not belong to a node-graph."; + } + return false; + } + + if (source) { + // Ensure that the source prim is a descendent of the node-graph owning + // the output. + const SdfPath sourcePrimPath = source.GetPrim().GetPath(); + const SdfPath outputPrimPath = output.GetPrim().GetPath(); + + if (not sourcePrimPath.HasPrefix(outputPrimPath)) { + if (reason) { + *reason = TfStringPrintf("Source of output '%s' on node-graph " + "at path <%s> is outside the node-graph: <%s>", + source.GetName().GetText(), outputPrimPath.GetText(), + sourcePrimPath.GetText()); + } + return false; + } + + } + + return true; +} + +bool +UsdShadeConnectableAPI::CanConnect( + const UsdShadeOutput &output, + const UsdAttribute &source) +{ + std::string reason; + // The reason why a connection can't be made isn't exposed currently. + // We may want to expose it in the future, especially when we have + // validation in USD. + return _CanConnectOutputToSource(output, source, &reason); +} + +bool +_CanConnectInputToSource(const UsdShadeInput &input, + const UsdAttribute &source, + std::string *reason) +{ + if (not input.IsDefined()) { + if (reason) { + *reason = TfStringPrintf("Invalid input: %s", + input.GetAttr().GetPath().GetText()); + } + return false; + } + + if (not source) { + if (reason) { + *reason = TfStringPrintf("Invalid source: %s", + source.GetPath().GetText()); + } + return false; + } + + TfToken inputConnectability = input.GetConnectability(); + if (inputConnectability == UsdShadeTokens->full) { + return true; + } else if (inputConnectability == UsdShadeTokens->interfaceOnly) { + if (UsdShadeInput::IsInput(source)) { + if (source.GetPrim().IsA()) { + return true; + } + + TfToken sourceConnectability = UsdShadeInput(source).GetConnectability(); + if (sourceConnectability == UsdShadeTokens->interfaceOnly) { + return true; + } + } + } + + if (reason) { + *reason = TfStringPrintf("Input connectability is 'interfaceOnly' and " + "source does not have 'interfaceOnly' connectability."); + } + + return false; +} + +bool +UsdShadeConnectableAPI::CanConnect( + const UsdShadeInput &input, + const UsdAttribute &source) +{ + std::string reason; + // The reason why a connection can't be made isn't exposed currently. + // We may want to expose it in the future, especially when we have + // validation in USD. + return _CanConnectInputToSource(input, source, &reason); +} + static TfToken _GetConnectionRelName(const TfToken &attrName) { @@ -176,15 +286,6 @@ _GetConnectionRel( return UsdRelationship(); } -static -TfToken -_GetPropertyName(const TfToken &sourceName, - const UsdShadeAttributeType sourceType) -{ - return TfToken(UsdShadeUtils::GetPrefixForAttributeType(sourceType) + - sourceName.GetString()); -} - /* static */ bool UsdShadeConnectableAPI::ConnectToSource( @@ -194,34 +295,6 @@ UsdShadeConnectableAPI::ConnectToSource( UsdShadeAttributeType const sourceType, SdfValueTypeName typeName) { - UsdShadeAttributeType shadingPropType = - UsdShadeUtils::GetBaseNameAndType(shadingProp.GetName()).second; - if (shadingPropType == UsdShadeAttributeType::Output) { - // Only outputs belonging to node-graphs are connectable. We don't allow - // connecting outputs of shaders as it's not meaningful. - // - // Note: this warning will not be issued if the prim is untyped or - // if the type is unknown. - if (UsdShadeConnectableAPI(shadingProp.GetPrim()).IsShader()) { - TF_WARN("Attempted to connect an output of a shader <%s> to <%s>.", - shadingProp.GetPath().GetText(), - source.GetPath().AppendProperty( - _GetPropertyName(sourceName, sourceType)).GetText()); - return false; - } - - // Ensure that the source prim is a descendent of the node-graph owning - // the output. - const SdfPath sourcePrimPath = source.GetPrim().GetPath(); - const SdfPath outputOwnerPath = shadingProp.GetPrim().GetPath(); - if (!sourcePrimPath.HasPrefix(outputOwnerPath)) { - TF_WARN("Source of output '%s' on node-graph at path <%s> is outside " - "the node-graph: <%s>", sourceName.GetText(), - outputOwnerPath.GetText(), sourcePrimPath.GetText()); - // Issue a warning, but allow this connnection for now. - } - } - UsdPrim sourcePrim = source.GetPrim(); bool success = true; diff --git a/pxr/usd/lib/usdShade/connectableAPI.h b/pxr/usd/lib/usdShade/connectableAPI.h index 0e2ed49d94..f6391bdff5 100644 --- a/pxr/usd/lib/usdShade/connectableAPI.h +++ b/pxr/usd/lib/usdShade/connectableAPI.h @@ -185,6 +185,31 @@ class UsdShadeConnectableAPI : public UsdSchemaBase /// /// @{ + /// Determines whether the given input can be connected to the given + /// source attribute, which can be an input or an output. + /// + /// The result depends on the "connectability" of the input and the source + /// attributes and the types of prims they belong to. + /// + /// \sa UsdShadeInput::SetConnectability + USDSHADE_API + static bool CanConnect(const UsdShadeInput &input, + const UsdAttribute &source); + + /// Determines whether the given output can be connected to the given + /// source attribute, which can be an input or an output. + /// + /// An output is considered to be connectable only if it belongs to a + /// node-graph. Shader outputs are not connectable. + /// + /// \p source is an optional argument. If a valid UsdAttribute is supplied + /// for it, this method will return true only if the source attribute is + /// owned by a descendant of the node-graph owning the output. + /// + USDSHADE_API + static bool CanConnect(const UsdShadeOutput &output, + const UsdAttribute &source=UsdAttribute()); + /// Authors a connection for a given shading property \p shadingProp. /// /// \p shadingProp can represent a parameter, an interface attribute or diff --git a/pxr/usd/lib/usdShade/input.cpp b/pxr/usd/lib/usdShade/input.cpp index 75606e687d..878d5f5693 100644 --- a/pxr/usd/lib/usdShade/input.cpp +++ b/pxr/usd/lib/usdShade/input.cpp @@ -44,6 +44,7 @@ using std::string; TF_DEFINE_PRIVATE_TOKENS( _tokens, + (connectability) (renderType) ); @@ -157,5 +158,49 @@ UsdShadeInput::IsInput(const UsdAttribute &attr) UsdShadeTokens->inputs)); } +bool +UsdShadeInput::SetConnectability(const TfToken &connectability) const +{ + return _attr.SetMetadata(_tokens->connectability, connectability); +} + +TfToken +UsdShadeInput::GetConnectability() const +{ + TfToken connectability; + _attr.GetMetadata(_tokens->connectability, &connectability); + + // If there's no authored connectability, then check the owner of the + // input to see if it's a node-graph or a shader. + // If it's a node-graph, the default connectability is "interfaceOnly". + // If it's a shader, the default connectability is "full". + // + // If the owner's type is unknown, then get the fallback value from the + // schema registry. + // + if (connectability.IsEmpty()) { + UsdShadeConnectableAPI connectable(GetPrim()); + if (connectable.IsNodeGraph()) + return UsdShadeTokens->interfaceOnly; + else if (connectable.IsShader()) { + return UsdShadeTokens->full; + } + } else { + return connectability; + } + + static const VtValue fallback = SdfSchema::GetInstance().GetFallback( + _tokens->connectability); + + return fallback.IsHolding() ? fallback.UncheckedGet() + : UsdShadeTokens->full; +} + +bool +UsdShadeInput::ClearConnectability() const +{ + return _attr.ClearMetadata(_tokens->connectability); +} + PXR_NAMESPACE_CLOSE_SCOPE diff --git a/pxr/usd/lib/usdShade/input.h b/pxr/usd/lib/usdShade/input.h index bd53106e4c..562b53bedc 100644 --- a/pxr/usd/lib/usdShade/input.h +++ b/pxr/usd/lib/usdShade/input.h @@ -176,6 +176,48 @@ class UsdShadeInput return lhs.GetAttr() == rhs.GetAttr(); } + // ------------------------------------------------------------------------- + /// \name Connectability API + // ------------------------------------------------------------------------- + /// @{ + + /// \brief Set the connectability of the Input. + /// + /// In certain shading data models, there is a need to distinguish which + /// inputs can vary over a surface from those that must be + /// uniform. This is accomplished in UsdShade by limiting the + /// connectability of the input. This is done by setting the + /// "connectability" metadata on the associated attribute. + /// + /// + /// Connectability of an Input can be set to UsdShadeTokens->full or + /// UsdShadeTokens->interfaceOnly. + /// + /// \li full implies that the Input can be connected to any other + /// Input or Output. + /// \li interfaceOnly implies that the Input can only be connected to + /// a NodeGraph Input (which represents an interface override, not a + /// render-time dataflow connection), or another Input whose connectability + /// is also "interfaceOnly". + /// + /// The default connectability of a node-graph interface input is + /// UsdShadeTokens->interfaceOnly. + /// The default connectability of a shader input is UsdShadeTokens->full. + /// + /// \sa SetConnectability() + bool SetConnectability(const TfToken &connectability) const; + + /// \brief Returns the connectability of the Input. + /// + /// \sa SetConnectability() + TfToken GetConnectability() const; + + /// \brief Clears any authored connectability on the Input. + /// + bool ClearConnectability() const; + + /// @} + private: friend class UsdShadeConnectableAPI; diff --git a/pxr/usd/lib/usdShade/plugInfo.json b/pxr/usd/lib/usdShade/plugInfo.json index e6db7ad893..dc5e8522fb 100644 --- a/pxr/usd/lib/usdShade/plugInfo.json +++ b/pxr/usd/lib/usdShade/plugInfo.json @@ -6,13 +6,14 @@ { "Info": { "SdfMetadata": { - "arrayConnectionSize": { + "connectability": { "appliesTo": [ "attributes" ], - "default": -1, + "default": "full", "displayGroup": "Shading", - "type": "int" + "documentation": "Metadata authored on UsdShadeInput's to specify what they can be connected to. Can be either \"full\" or \"interfaceOnly\". \"full\" implies that the input can be connected to any other input or output. \"interfaceOnly\" implies that the input can only connect to a NodeGraph Input (which represents an interface override, not a render-time dataflow connection), or another Input whose connectability is also \"interfaceOnly\".", + "type": "token" }, "outputName": { "appliesTo": [ diff --git a/pxr/usd/lib/usdShade/schema.usda b/pxr/usd/lib/usdShade/schema.usda index cb34496788..43e0bef25a 100644 --- a/pxr/usd/lib/usdShade/schema.usda +++ b/pxr/usd/lib/usdShade/schema.usda @@ -48,6 +48,21 @@ over "GLOBAL" ( UsdShadeMaterial. """ } + dictionary full = { + string doc= """Possible value for 'connectability' metadata on + a UsdShadeInput. When connectability of an input is set to + "full", it implies that it can be connected to any input or + output. + """ + } + dictionary interfaceOnly = { + string doc= """Possible value for 'connectability' metadata on + a UsdShadeInput. It implies that the input can only connect to + a NodeGraph Input (which represents an interface override, not + a render-time dataflow connection), or another Input whose + connectability is also 'interfaceOnly'. + """ + } dictionary connectedSourceFor = { string value = "connectedSourceFor:" string doc = """The prefix on UsdShadeShader relationships diff --git a/pxr/usd/lib/usdShade/tokens.h b/pxr/usd/lib/usdShade/tokens.h index f542653869..51fc965690 100644 --- a/pxr/usd/lib/usdShade/tokens.h +++ b/pxr/usd/lib/usdShade/tokens.h @@ -46,9 +46,11 @@ PXR_NAMESPACE_OPEN_SCOPE (displacement) \ (displayColor) \ (displayOpacity) \ + (full) \ ((infoId, "info:id")) \ ((inputs, "inputs:")) \ ((interface_, "interface:")) \ + (interfaceOnly) \ ((interfaceRecipientsOf, "interfaceRecipientsOf:")) \ ((lookBinding, "look:binding")) \ ((materialBinding, "material:binding")) \ @@ -84,9 +86,11 @@ PXR_NAMESPACE_OPEN_SCOPE /// \li displacement - Describes the displacement relationship terminal on a UsdShadeMaterial. Used to find the terminal UsdShadeShader describing the displacement of a UsdShadeMaterial. /// \li displayColor - UsdShadePShader /// \li displayOpacity - UsdShadePShader +/// \li full - Possible value for 'connectability' metadata on a UsdShadeInput. When connectability of an input is set to "full", it implies that it can be connected to any input or output. /// \li infoId - UsdShadeShader /// \li inputs - The prefix on shading attributes denoting an input. /// \li interface_ - The prefix on UsdShadeNodeGraph attributes denoting an interface attribute. +/// \li interfaceOnly - Possible value for 'connectability' metadata on a UsdShadeInput. It implies that the input can only connect to a NodeGraph Input (which represents an interface override, not a render-time dataflow connection), or another Input whose connectability is also 'interfaceOnly'. /// \li interfaceRecipientsOf - The prefix on UsdShadeNodeGraph relationships denoting the target of an interface attribute. /// \li lookBinding - The relationship name on non shading prims to denote a binding to a UsdShadeLook. This is a deprecated relationship and is superceded by material:binding. /// \li materialBinding - The relationship name on non-shading prims to denote a binding to a UsdShadeMaterial. diff --git a/pxr/usd/lib/usdShade/wrapConnectableAPI.cpp b/pxr/usd/lib/usdShade/wrapConnectableAPI.cpp index b0d63934c1..aba0097f24 100644 --- a/pxr/usd/lib/usdShade/wrapConnectableAPI.cpp +++ b/pxr/usd/lib/usdShade/wrapConnectableAPI.cpp @@ -104,6 +104,8 @@ PXR_NAMESPACE_CLOSE_SCOPE PXR_NAMESPACE_OPEN_SCOPE +#include + static object _GetConnectedSource(const UsdProperty &shadingProp) { @@ -113,7 +115,7 @@ _GetConnectedSource(const UsdProperty &shadingProp) if (UsdShadeConnectableAPI::GetConnectedSource(shadingProp, &source, &sourceName, &sourceType)){ - return make_tuple(source, sourceName, sourceType); + return boost::python::make_tuple(source, sourceName, sourceType); } else { return object(); } @@ -141,6 +143,14 @@ WRAP_CUSTOM { UsdProperty const &, UsdShadeOutput const &) = &UsdShadeConnectableAPI::ConnectToSource; + bool (*CanConnect_Input)( + UsdShadeInput const &, + UsdAttribute const &) = &UsdShadeConnectableAPI::CanConnect; + + bool (*CanConnect_Output)( + UsdShadeOutput const &, + UsdAttribute const &) = &UsdShadeConnectableAPI::CanConnect; + _class .def(init(arg("shader"))) .def(init(arg("nodeGraph"))) @@ -148,13 +158,19 @@ WRAP_CUSTOM { .def("IsShader", &UsdShadeConnectableAPI::IsShader) .def("IsNodeGraph", &UsdShadeConnectableAPI::IsNodeGraph) + .def("CanConnect", CanConnect_Input, + (arg("input"), arg("source"))) + .def("CanConnect", CanConnect_Output, + (arg("output"), arg("source")=UsdAttribute())) + .staticmethod("CanConnect") + .def("ConnectToSource", ConnectToSource_1, (arg("shadingProp"), arg("source"), arg("sourceName"), arg("sourceType")=UsdShadeAttributeType::Output, arg("typeName")=SdfValueTypeName())) .def("ConnectToSource", ConnectToSource_2, - (arg("shadingProp"), arg("path"))) + (arg("shadingProp"), arg("sourcePath"))) .def("ConnectToSource", ConnectToSource_3, (arg("shadingProp"), arg("input"))) .def("ConnectToSource", ConnectToSource_4, diff --git a/pxr/usd/lib/usdShade/wrapInput.cpp b/pxr/usd/lib/usdShade/wrapInput.cpp index 62a5e5f055..ff3bf89fce 100644 --- a/pxr/usd/lib/usdShade/wrapInput.cpp +++ b/pxr/usd/lib/usdShade/wrapInput.cpp @@ -69,6 +69,10 @@ void wrapUsdShadeInput() .def("GetRenderType", &Input::GetRenderType) .def("HasRenderType", &Input::HasRenderType) + .def("SetConnectability", &Input::SetConnectability) + .def("GetConnectability", &Input::GetConnectability) + .def("ClearConnectability", &Input::ClearConnectability) + .def("GetAttr", &Input::GetAttr, return_value_policy()) ;