Skip to content

Commit

Permalink
Update UsdShadeMaterialBinding::ComputeBoundMaterial
Browse files Browse the repository at this point in the history
- Previous if the target material on the material binding relationship was "unloaded/deactivated"
  that is not found on the stage, then the ComputeBoundMaterial logic used to not consider this
  as the winning binding even though all rules were satisfied for the binding to be a winning binding.

- In the new and more correct approach, instead of checking on the validity of the Material prim on
  the stage, it checks if the target path for the binding being considered is a valid path (non-empty path). This makes
  sure the boundMaterial and the winning binding relationship remains the same irrespective of the
  load/unload (active/deactivated) state of the target prim on the material binding relationship.
  If the prim at target path exists on stage but is not a UsdShadeMaterial, ComputeBoundMaterial
  returns an invalid UsdShadeMaterial but with still with the bindingRel targeting the prim.

- Adds static UsdShadeMaterialBinding::GetResolvedTargetPathFromBindingRel
  This can help client get to the path of the target of the winning binding relationship specially when
  no valid Material Prim exists at the target path of the bindingRel returned by ComputeMaterialBound.

- Adds a helper static UsdShadeMaterialBinding::CollectionBinding::IsCollectionBindingRel
  To check if the bindingRel returned by ComputeBoundMaterial a relationship idenitifying a collection or not.

(Internal change: 2248004)
  • Loading branch information
tallytalwar authored and pixar-oss committed Sep 7, 2022
1 parent 6246934 commit 1618739
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 32 deletions.
60 changes: 51 additions & 9 deletions pxr/usd/usdShade/materialBindingAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,16 @@ UsdShadeMaterialBindingAPI::CollectionBinding::GetCollection() const
return UsdCollectionAPI();
}

/* static */
bool
UsdShadeMaterialBindingAPI::CollectionBinding::IsCollectionBindingRel(
const UsdRelationship &bindingRel)
{
return TfStringStartsWith(bindingRel.GetName(),
SdfPath::JoinIdentifier(UsdShadeTokens->materialBinding,
UsdTokens->collection));
}


UsdShadeMaterialBindingAPI::CollectionBindingVector
UsdShadeMaterialBindingAPI::GetCollectionBindings(
Expand Down Expand Up @@ -642,10 +652,10 @@ UsdShadeMaterialBindingAPI::BindingsAtPrim::BindingsAtPrim(
directBinding.reset(new DirectBinding(directBindingRel));
}

// If there is no restricted purpose direct binding, look for an
// all-purpose direct-binding.
// If there is no restricted purpose direct binding or an empty material
// path on the direct binding, look for an all-purpose direct-binding.
if (materialPurpose != UsdShadeTokens->allPurpose &&
(!directBinding || !directBinding->GetMaterial())) {
(!directBinding || directBinding->GetMaterialPath().IsEmpty())) {

// This may not be necessary if a specific purpose collection-binding
// already includes the prim for which the resolved binding is being
Expand All @@ -660,8 +670,8 @@ UsdShadeMaterialBindingAPI::BindingsAtPrim::BindingsAtPrim(
}
}

// If the direct-binding points to an invalid material then clear it.
if (directBinding && !directBinding->GetMaterial()) {
// If the direct-binding points to an empty material path then clear it.
if (directBinding && directBinding->GetMaterialPath().IsEmpty()) {
directBinding.release();
}

Expand Down Expand Up @@ -708,6 +718,23 @@ UsdShadeMaterialBindingAPI::GetMaterialPurposes()
UsdShadeTokens->full };
}

/* static */
const SdfPath
UsdShadeMaterialBindingAPI::GetResolvedTargetPathFromBindingRel(
const UsdRelationship &bindingRel)
{
if (!bindingRel) {
return SdfPath();
}

SdfPathVector targetPaths;
bindingRel.GetForwardedTargets(&targetPaths);

return
UsdShadeMaterialBindingAPI::CollectionBinding::IsCollectionBindingRel(
bindingRel) ? targetPaths[1] : targetPaths[0];
}

UsdShadeMaterial
UsdShadeMaterialBindingAPI::ComputeBoundMaterial(
BindingsCache *bindingsCache,
Expand All @@ -730,6 +757,7 @@ UsdShadeMaterialBindingAPI::ComputeBoundMaterial(

for (auto const & purpose : materialPurposes) {
UsdShadeMaterial boundMaterial;
bool hasValidTargetPath = false;
UsdRelationship winningBindingRel;

for (UsdPrim p = GetPrim(); !p.IsPseudoRoot(); p = p.GetParent()) {
Expand All @@ -754,15 +782,18 @@ UsdShadeMaterialBindingAPI::ComputeBoundMaterial(

const DirectBindingPtr &directBindingPtr =
bindingsAtP.directBinding;

if (directBindingPtr &&
directBindingPtr->GetMaterialPurpose() == purpose)
{
const UsdRelationship &directBindingRel =
directBindingPtr->GetBindingRel();
if (!boundMaterial ||
if (!hasValidTargetPath ||
(GetMaterialBindingStrength(directBindingRel) ==
UsdShadeTokens->strongerThanDescendants))
{
hasValidTargetPath =
!directBindingPtr->GetMaterialPath().IsEmpty();
boundMaterial = directBindingPtr->GetMaterial();
winningBindingRel = directBindingRel;
}
Expand Down Expand Up @@ -805,10 +836,13 @@ UsdShadeMaterialBindingAPI::ComputeBoundMaterial(
// If the collection binding is on the prim itself and if
// the prim is included in the collection, the collection-based
// binding is considered to be stronger than the direct binding.
if (!boundMaterial ||
(boundMaterial && winningBindingRel.GetPrim() == p) ||
if (!hasValidTargetPath ||
(hasValidTargetPath &&
winningBindingRel.GetPrim() == p) ||
(GetMaterialBindingStrength(collBindingRel) ==
UsdShadeTokens->strongerThanDescendants)) {
hasValidTargetPath =
!collBinding.GetMaterialPath().IsEmpty();
boundMaterial = collBinding.GetMaterial();
winningBindingRel = collBindingRel;

Expand All @@ -821,11 +855,19 @@ UsdShadeMaterialBindingAPI::ComputeBoundMaterial(
}

// The first "purpose" with a valid binding wins.
if (boundMaterial) {
if (hasValidTargetPath) {
if (bindingRel) {
*bindingRel = winningBindingRel;
}

// Make sure the bound material is a UsdShadeMaterial, ie:
// - it exists on the stage as a UsdShadeMaterial
// This is to make sure any non-material type target on the
// bindingRel is not returned as the boundMaterial!
if (boundMaterial.GetPrim()) {
return (boundMaterial.GetPrim().IsA<UsdShadeMaterial>()) ?
boundMaterial : UsdShadeMaterial();
}
return boundMaterial;
}
}
Expand Down
27 changes: 23 additions & 4 deletions pxr/usd/usdShade/materialBindingAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -378,11 +378,16 @@ class UsdShadeMaterialBindingAPI : public UsdAPISchemaBase
/// that is bound by this collection-binding.
USDSHADE_API
UsdCollectionAPI GetCollection() const;

/// Checks if the \p bindingRel identifies a collection
USDSHADE_API
static bool IsCollectionBindingRel(const UsdRelationship &bindingRel);

/// Returns true if the CollectionBinding points to a valid material
/// and collection.
/// Returns true if the CollectionBinding points to a non-empty material
/// path and collection.
bool IsValid() const {
return GetCollection() && GetMaterial();
return CollectionBinding::IsCollectionBindingRel(_bindingRel)
&& !GetMaterialPath().IsEmpty();
}
/// Returns the path to the collection that is bound by this binding.
const SdfPath &GetCollectionPath() const {
Expand Down Expand Up @@ -711,7 +716,11 @@ class UsdShadeMaterialBindingAPI : public UsdAPISchemaBase
USDSHADE_API
static TfTokenVector GetMaterialPurposes();

/// \overload
/// returns the path of the resolved target identified by \p bindingRel.
USDSHADE_API
static const SdfPath GetResolvedTargetPathFromBindingRel(
const UsdRelationship &bindingRel);

/// Computes the resolved bound material for this prim, for the given
/// material purpose.
///
Expand Down Expand Up @@ -746,6 +755,16 @@ class UsdShadeMaterialBindingAPI : public UsdAPISchemaBase
/// If \p bindingRel is not null, then it is set to the "winning" binding
/// relationship.
///
/// Note the resolved bound material is considered valid if the target path
/// of the binding relationship is a valid non-empty prim path. This makes
/// sure winning binding relationship and the bound material remain consistent
/// consistent irrespective of the presence/absence of prim at material
/// path. For ascenario where ComputeBoundMaterial returns a invalid
/// UsdShadeMaterial with a valid winning bindingRel, clients can use the
/// static method
/// UsdShadeMaterialBindingAPI::GetResolvedTargetPathFromBindingRel to get
/// the path of the resolved target identified by the winning bindingRel.
///
/// See \ref UsdShadeMaterialBindingAPI_MaterialResolution "Bound Material Resolution"
/// for details on the material resolution process.
///
Expand Down
96 changes: 96 additions & 0 deletions pxr/usd/usdShade/testenv/testUsdShadeMaterialBinding.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,102 @@ def test_CollectionBindingWithPurpose(self):
materialPurpose=UsdShade.Tokens.full).GetPath(),
redMat.GetPath())

def test_DeactivatedMaterialBindings(self):
stage = Usd.Stage.Open("testDeactivatedMaterialBindings.usda")

# /Geometry/InnerScope/gprim and other prims in a collection are
# assigned a material which is under a deactivated scope, test to make
# sure we still return the same binding, but with an invalid material
# prim.
gprim = stage.GetPrimAtPath("/Geometry/InnerScope/gprim")
deactivatedLooks1 = stage.GetPrimAtPath("/Looks/DeactivatedLooks1")
materialBinding = UsdShade.MaterialBindingAPI(gprim)

# Deactivated materials
self.assertFalse(deactivatedLooks1.IsActive())
computedBoundMaterial = materialBinding.ComputeBoundMaterial()
self.assertFalse(computedBoundMaterial[0])

expectedMaterialPath = Sdf.Path('/Looks/DeactivatedLooks1/GlobalMat1')
expectedBindingRel = \
Sdf.Path('/Geometry/InnerScope/gprim.material:binding')
self.assertEqual(
UsdShade.MaterialBindingAPI.GetResolvedTargetPathFromBindingRel(
computedBoundMaterial[1]), expectedMaterialPath)
self.assertEqual(computedBoundMaterial[1].GetPath(),
expectedBindingRel)
self.assertFalse(UsdShade.MaterialBindingAPI.CollectionBinding. \
IsCollectionBindingRel(computedBoundMaterial[1]))

# Active materials
deactivatedLooks1.SetActive(True)
self.assertTrue(deactivatedLooks1.IsActive())
computedBoundMaterial = materialBinding.ComputeBoundMaterial()
self.assertTrue(computedBoundMaterial[0])
self.assertEqual(
UsdShade.MaterialBindingAPI.GetResolvedTargetPathFromBindingRel(
computedBoundMaterial[1]),
expectedMaterialPath)
self.assertEqual(computedBoundMaterial[1].GetPath(),
expectedBindingRel)
self.assertFalse(UsdShade.MaterialBindingAPI.CollectionBinding. \
IsCollectionBindingRel(computedBoundMaterial[1]))

# Lets try a collection binding
s1 = stage.GetPrimAtPath("/Collections/s1")
deactivatedLooks2 = stage.GetPrimAtPath("/Looks/DeactivatedLooks2")
materialBinding = UsdShade.MaterialBindingAPI(s1)

# Deactivated materials
self.assertFalse(deactivatedLooks2.IsActive())
computedBoundMaterial = materialBinding.ComputeBoundMaterial()
self.assertFalse(computedBoundMaterial[0])

expectedMaterialPath = Sdf.Path('/Looks/DeactivatedLooks2/GlobalMat1')
expectedBindingRel = \
Sdf.Path('/Collections.material:binding:collection:bindSet1')
self.assertEqual(
UsdShade.MaterialBindingAPI.GetResolvedTargetPathFromBindingRel(
computedBoundMaterial[1]), expectedMaterialPath)
self.assertEqual(computedBoundMaterial[1].GetPath(),
expectedBindingRel)
self.assertTrue(UsdShade.MaterialBindingAPI.CollectionBinding. \
IsCollectionBindingRel(computedBoundMaterial[1]))

# Active materials
deactivatedLooks2.SetActive(True)
self.assertTrue(deactivatedLooks2.IsActive())
computedBoundMaterial = materialBinding.ComputeBoundMaterial()
self.assertTrue(computedBoundMaterial[0])
self.assertEqual(
UsdShade.MaterialBindingAPI.GetResolvedTargetPathFromBindingRel(
computedBoundMaterial[1]), expectedMaterialPath)
self.assertEqual(computedBoundMaterial[1].GetPath(),
expectedBindingRel)
self.assertTrue(UsdShade.MaterialBindingAPI.CollectionBinding. \
IsCollectionBindingRel(computedBoundMaterial[1]))

# Lets try a over prim bound on material binding
# - For such a prim, ComputeBoundMaterial should return an invalid
# UsdShadeMaterial with the same winning bindingRel.
s2 = stage.GetPrimAtPath("/Collections/s2")
materialBinding = UsdShade.MaterialBindingAPI(s2)
overPrim = stage.GetPrimAtPath("/OverPrims/GlobalMat1")
self.assertFalse(overPrim.IsA("UsdShadeMaterial"))

expectedBindingRel = \
Sdf.Path('/Collections.material:binding:collection:bindSet2')
computedBoundMaterial = materialBinding.ComputeBoundMaterial()
self.assertFalse(computedBoundMaterial[0])
boundTargetPath = UsdShade.MaterialBindingAPI. \
GetResolvedTargetPathFromBindingRel(computedBoundMaterial[1])
self.assertFalse(
stage.GetPrimAtPath(boundTargetPath).IsA("UsdShadeMaterial"))
self.assertEqual(boundTargetPath, overPrim.GetPath())
self.assertEqual(computedBoundMaterial[1].GetPath(), expectedBindingRel)
self.assertTrue(UsdShade.MaterialBindingAPI.CollectionBinding. \
IsCollectionBindingRel(computedBoundMaterial[1]))

def test_BlockingOnOver(self):
stage = Usd.Stage.CreateInMemory()
over = stage.OverridePrim('/World/over')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#usda 1.0

def Xform "Geometry" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Looks/GlobalMat1>

def Xform "InnerScope"
{
def Sphere "gprim" (
prepend apiSchemas = ["MaterialBindingAPI"]
)
{
rel material:binding = </Looks/DeactivatedLooks1/GlobalMat1>
}
}
}

def Scope "Collections" (
prepend apiSchemas = ["CollectionAPI:Set1", "CollectionAPI:Set2", "MaterialBindingAPI"]
)
{
uniform token collection:Set1:expansionRule = "expandPrims"
rel collection:Set1:includes = [
</Collections/s1>,
</Collections/c1>,
]

uniform token collection:Set2:expansionRule = "expandPrims"
rel collection:Set2:includes = [
</Collections/s2>,
</Collections/Cubes>,
]
rel collection:Set2:excludes = </Collections/Cubes/c2e>

rel material:binding:collection:bindSet2 = [
</Collections.collection:Set2>,
</OverPrims/GlobalMat1>
]

rel material:binding:collection:bindSet1 = [
</Collections.collection:Set1>,
</Looks/DeactivatedLooks2/GlobalMat1>
]

rel material:binding = </Looks/GlobalMat1>

def Sphere "s1"
{
}

def Cube "c1"
{
}

def Sphere "s2"
{
}

def Xform "Cubes"
{
def Cube "c2"
{
}

def Cube "c2e"
{
}
}
}

def Scope "OverPrims"
{
over "GlobalMat1"
{

}
}

def Scope "Looks"
{
def Material "GlobalMat1" (
)
{
}

def Scope "DeactivatedLooks1" (
active = false
)
{
def Material "GlobalMat1" (
)
{
}
}
def Scope "DeactivatedLooks2" (
active = false
)
{
def Material "GlobalMat1" (
)
{
}
}
}
Loading

0 comments on commit 1618739

Please sign in to comment.