-
Notifications
You must be signed in to change notification settings - Fork 201
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
LOOKDEVX-1592 - Clean up references, targets, and connections on delete #3194
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,6 +107,36 @@ void replaceInternalReferencePath( | |
} | ||
} | ||
|
||
void removeInternalReferencePath( | ||
const UsdPrim& deletedPrim, | ||
const SdfReferencesProxy& referencesList, | ||
SdfListOpType op) | ||
{ | ||
// set the listProxy based on the SdfListOpType | ||
SdfReferencesProxy::ListProxy listProxy = referencesList.GetAppendedItems(); | ||
if (op == SdfListOpTypePrepended) { | ||
listProxy = referencesList.GetPrependedItems(); | ||
} else if (op == SdfListOpTypeOrdered) { | ||
listProxy = referencesList.GetOrderedItems(); | ||
} else if (op == SdfListOpTypeAdded) { | ||
listProxy = referencesList.GetAddedItems(); | ||
} else if (op == SdfListOpTypeDeleted) { | ||
listProxy = referencesList.GetDeletedItems(); | ||
} | ||
|
||
for (size_t idx = 0; idx < listProxy.size();) { | ||
const SdfReference ref = listProxy[idx]; | ||
if (UsdUfe::isInternalReference(ref)) { | ||
if (deletedPrim.GetPath() == ref.GetPrimPath() | ||
|| ref.GetPrimPath().HasPrefix(deletedPrim.GetPath())) { | ||
listProxy.Erase(idx); | ||
continue; // Not increasing idx | ||
} | ||
} | ||
++idx; | ||
} | ||
} | ||
|
||
// This template method updates the SdfPath for inherited or specialized arcs | ||
// when the path to the concrete prim they refer to has changed. | ||
// HS January 13, 2021: Find a better generic way to consolidate this method with | ||
|
@@ -143,6 +173,34 @@ void replacePath(const UsdPrim& oldPrim, const SdfPath& newPath, const T& proxy, | |
} | ||
} | ||
|
||
// This template method cleans the SdfPath for inherited or specialized arcs | ||
// when the path to the concrete prim they refer to has becomed invalid. | ||
// HS January 13, 2021: Find a better generic way to consolidate this method with | ||
// removeReferenceItems | ||
template <typename T> void removePath(const UsdPrim& deletedPrim, const T& proxy, SdfListOpType op) | ||
{ | ||
// set the listProxy based on the SdfListOpType | ||
typename T::ListProxy listProxy { proxy.GetAppendedItems() }; | ||
if (op == SdfListOpTypePrepended) { | ||
listProxy = proxy.GetPrependedItems(); | ||
} else if (op == SdfListOpTypeOrdered) { | ||
listProxy = proxy.GetOrderedItems(); | ||
} else if (op == SdfListOpTypeAdded) { | ||
listProxy = proxy.GetAddedItems(); | ||
} else if (op == SdfListOpTypeDeleted) { | ||
listProxy = proxy.GetDeletedItems(); | ||
} | ||
|
||
for (size_t idx = 0; idx < listProxy.size();) { | ||
const SdfPath path = listProxy[idx]; | ||
if (deletedPrim.GetPath() == path.GetPrimPath() || path.HasPrefix(deletedPrim.GetPath())) { | ||
listProxy.Erase(idx); | ||
continue; // Not increasing idx | ||
} | ||
++idx; | ||
} | ||
} | ||
|
||
void replacePropertyPath(const UsdPrim& oldPrim, const SdfPath& newPath, UsdProperty& prop) | ||
{ | ||
if (prop.Is<UsdAttribute>()) { | ||
|
@@ -180,6 +238,58 @@ void replacePropertyPath(const UsdPrim& oldPrim, const SdfPath& newPath, UsdProp | |
} | ||
} | ||
|
||
void removePropertyPath(const UsdPrim& deletedPrim, UsdProperty& prop) | ||
{ | ||
if (prop.Is<UsdAttribute>()) { | ||
UsdAttribute attr = prop.As<UsdAttribute>(); | ||
SdfPathVector sources; | ||
attr.GetConnections(&sources); | ||
bool hasChanged = false; | ||
for (size_t i = 0; i < sources.size();) { | ||
const SdfPath& path = sources[i]; | ||
if (deletedPrim.GetPath() == path.GetPrimPath() | ||
|| path.HasPrefix(deletedPrim.GetPath())) { | ||
hasChanged = true; | ||
sources.erase(sources.cbegin() + i); | ||
continue; | ||
} | ||
++i; | ||
} | ||
if (hasChanged) { | ||
if (sources.empty()) { | ||
attr.ClearConnections(); | ||
if (!attr.HasValue()) { | ||
prop.GetPrim().RemoveProperty(prop.GetName()); | ||
} | ||
} else { | ||
attr.SetConnections(sources); | ||
} | ||
} | ||
} else if (prop.Is<UsdRelationship>()) { | ||
UsdRelationship rel = prop.As<UsdRelationship>(); | ||
SdfPathVector targets; | ||
rel.GetTargets(&targets); | ||
bool hasChanged = false; | ||
for (size_t i = 0; i < targets.size();) { | ||
const SdfPath& path = targets[i]; | ||
if (deletedPrim.GetPath() == path.GetPrimPath() | ||
|| path.HasPrefix(deletedPrim.GetPath())) { | ||
hasChanged = true; | ||
targets.erase(targets.cbegin() + i); | ||
continue; | ||
} | ||
++i; | ||
} | ||
if (hasChanged) { | ||
if (targets.empty()) { | ||
rel.ClearTargets(true); | ||
} else { | ||
rel.SetTargets(targets); | ||
} | ||
} | ||
} | ||
} | ||
|
||
} // namespace | ||
|
||
namespace USDUFE_NS_DEF { | ||
|
@@ -261,6 +371,53 @@ bool updateReferencedPath(const UsdPrim& oldPrim, const SdfPath& newPath) | |
return true; | ||
} | ||
|
||
bool cleanReferencedPath(const UsdPrim& deletedPrim) | ||
{ | ||
SdfChangeBlock changeBlock; | ||
|
||
for (const auto& p : deletedPrim.GetStage()->Traverse()) { | ||
|
||
auto primSpec = getPrimSpecAtEditTarget(p); | ||
// check different composition arcs | ||
if (p.HasAuthoredReferences()) { | ||
if (primSpec) { | ||
|
||
SdfReferencesProxy referencesList = primSpec->GetReferenceList(); | ||
|
||
// update append/prepend lists individually | ||
removeInternalReferencePath(deletedPrim, referencesList, SdfListOpTypeAppended); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Even in the previously-existing version for rename, IDK why we don't treat the SdfListOpTypeOrdered, SdfListOpTypeAdded, SdfListOpTypeExplicit and SdfListOpTypeDeleted. MayaUSd might not author those, but we can edit USD file that may contain them. |
||
removeInternalReferencePath(deletedPrim, referencesList, SdfListOpTypePrepended); | ||
} | ||
} else if (p.HasAuthoredInherits()) { | ||
if (primSpec) { | ||
|
||
SdfInheritsProxy inheritsList = primSpec->GetInheritPathList(); | ||
|
||
// update append/prepend lists individually | ||
removePath<SdfInheritsProxy>(deletedPrim, inheritsList, SdfListOpTypeAppended); | ||
removePath<SdfInheritsProxy>(deletedPrim, inheritsList, SdfListOpTypePrepended); | ||
} | ||
} else if (p.HasAuthoredSpecializes()) { | ||
if (primSpec) { | ||
SdfSpecializesProxy specializesList = primSpec->GetSpecializesList(); | ||
|
||
// update append/prepend lists individually | ||
removePath<SdfSpecializesProxy>( | ||
deletedPrim, specializesList, SdfListOpTypeAppended); | ||
removePath<SdfSpecializesProxy>( | ||
deletedPrim, specializesList, SdfListOpTypePrepended); | ||
} | ||
} | ||
|
||
// Need to repath connections and relationships: | ||
for (auto& prop : p.GetProperties()) { | ||
removePropertyPath(deletedPrim, prop); | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
bool isInternalReference(const SdfReference& ref) { return ref.IsInternal(); } | ||
|
||
} // namespace USDUFE_NS_DEF |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,7 +29,7 @@ | |
|
||
import ufe | ||
|
||
from pxr import Sdf | ||
from pxr import Sdf, UsdShade | ||
|
||
import os | ||
import unittest | ||
|
@@ -526,6 +526,82 @@ def testDeleteAndRemoveConnections(self): | |
self.assertFalse(surface2Prim.HasProperty('outputs:out')) | ||
self.assertFalse(surface3Prim.HasProperty('inputs:bsdf')) | ||
|
||
def testDeleteAndRemoveReference(self): | ||
'''Test deleting a prim and its references''' | ||
|
||
# open treeRef.ma scene in testSamples | ||
mayaUtils.openTreeRefScene() | ||
|
||
# validate the default edit target to be the Rootlayer. | ||
mayaPathSegment = mayaUtils.createUfePathSegment('|TreeRef_usd|TreeRef_usdShape') | ||
stage = mayaUsd.ufe.getStage(str(mayaPathSegment)) | ||
self.assertTrue(stage) | ||
self.assertEqual(stage.GetEditTarget().GetLayer(), stage.GetRootLayer()) | ||
self.assertTrue(stage.GetPrimAtPath('/TreeBase/leaf_ref_1').HasAuthoredReferences()) | ||
|
||
# delete the greenLeaf and check the internal references were cleaned: | ||
cmds.delete('|TreeRef_usd|TreeRef_usdShape,/TreeBase/leavesXform/greenLeaf') | ||
self.assertFalse(stage.GetPrimAtPath('/TreeBase/leaf_ref_1').HasAuthoredReferences()) | ||
# Ref2 has an "external" reference, but to this file. It will not be | ||
# cleaned up because external, baut maybe it should have since the | ||
# referenced file happens to be the root layer? | ||
self.assertTrue(stage.GetPrimAtPath('/TreeBase/leaf_ref_2').HasAuthoredReferences()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, might be good to file a story to improve this, along with my comments about other list ops. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also small typo baut -> but, no need to respin teh PR just for that in a unit test though |
||
|
||
# validate undo | ||
cmds.undo() | ||
ref1 = stage.GetPrimAtPath('/TreeBase/leaf_ref_1') | ||
self.assertTrue(stage.GetPrimAtPath('/TreeBase/leaf_ref_1').HasAuthoredReferences()) | ||
|
||
# validate redo | ||
cmds.redo() | ||
self.assertFalse(stage.GetPrimAtPath('/TreeBase/leaf_ref_1').HasAuthoredReferences()) | ||
|
||
# Try deleting leavesXform hierarchy, which also deletes greenLeaf and | ||
# should clean-up the reference. | ||
cmds.undo() | ||
cmds.delete('|TreeRef_usd|TreeRef_usdShape,/TreeBase/leavesXform') | ||
self.assertFalse(stage.GetPrimAtPath('/TreeBase/leaf_ref_1').HasAuthoredReferences()) | ||
|
||
# validate undo | ||
cmds.undo() | ||
self.assertTrue(stage.GetPrimAtPath('/TreeBase/leaf_ref_1').HasAuthoredReferences()) | ||
|
||
# validate redo | ||
cmds.redo() | ||
self.assertFalse(stage.GetPrimAtPath('/TreeBase/leaf_ref_1').HasAuthoredReferences()) | ||
|
||
def testDeleteAndInheritMaterial(self): | ||
'''Test deleting a material cleans up all places where it was assigned''' | ||
|
||
cmds.file(new=True, force=True) | ||
testFile = testUtils.getTestScene('tree', 'treeMat.usda') | ||
pathString,stage = mayaUtils.createProxyFromFile(testFile) | ||
self.assertTrue(stage) | ||
self.assertTrue(pathString) | ||
matAPI = UsdShade.MaterialBindingAPI(stage.GetPrimAtPath('/TreeBase/leavesXform/leaves')) | ||
|
||
self.assertEqual(matAPI.ComputeBoundMaterial()[0].GetPrim().GetPath(), '/mtlFoliage/GreenMat') | ||
|
||
cmds.delete(pathString + ',/mtlFoliage/GreenMat') | ||
self.assertEqual(matAPI.ComputeBoundMaterial()[0].GetPrim().GetPath(), '/mtl/BrownMat') | ||
|
||
cmds.undo() | ||
self.assertEqual(matAPI.ComputeBoundMaterial()[0].GetPrim().GetPath(), '/mtlFoliage/GreenMat') | ||
|
||
cmds.redo() | ||
self.assertEqual(matAPI.ComputeBoundMaterial()[0].GetPrim().GetPath(), '/mtl/BrownMat') | ||
|
||
cmds.undo() | ||
cmds.delete(pathString + ',/mtlFoliage') | ||
self.assertEqual(matAPI.ComputeBoundMaterial()[0].GetPrim().GetPath(), '/mtl/BrownMat') | ||
|
||
cmds.undo() | ||
self.assertEqual(matAPI.ComputeBoundMaterial()[0].GetPrim().GetPath(), '/mtlFoliage/GreenMat') | ||
|
||
cmds.redo() | ||
self.assertEqual(matAPI.ComputeBoundMaterial()[0].GetPrim().GetPath(), '/mtl/BrownMat') | ||
|
||
|
||
def testDeleteRestrictionMutedLayer(self): | ||
''' | ||
Test delete restriction - we don't allow removal of a prim | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
#usda 1.0 | ||
( | ||
defaultPrim = "TreeBase" | ||
upAxis = "Y" | ||
) | ||
|
||
def Xform "TreeBase" ( | ||
prepend apiSchemas = ["MaterialBindingAPI"] | ||
) | ||
{ | ||
rel material:binding = </mtl/BrownMat> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This material will be propagated to all children. |
||
|
||
def Xform "leavesXform" ( | ||
prepend apiSchemas = ["MaterialBindingAPI"] | ||
) | ||
{ | ||
rel material:binding = </mtlFoliage/GreenMat> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This material wins over the parent material. |
||
double3 xformOp:translate = (0, 2.5, 0) | ||
uniform token[] xformOpOrder = ["xformOp:translate"] | ||
|
||
def Sphere "leaves" | ||
{ | ||
float3[] extent = [(-2, -2, -2), (2, 2, 2)] | ||
color3f[] primvars:displayColor = [(0, 1, 0)] | ||
double radius = 2 | ||
} | ||
} | ||
|
||
def Cylinder "trunk" ( | ||
active = true | ||
kind = "component" | ||
) | ||
{ | ||
uniform token axis = "Y" | ||
float3[] extent = [(-1, -1, -1), (1, 1, 1)] | ||
double height = 2 | ||
color3f[] primvars:displayColor = [(0.66, 0.33, 0)] | ||
double radius = 0.5 | ||
} | ||
} | ||
|
||
def Scope "mtl" | ||
{ | ||
def Material "BrownMat" | ||
{ | ||
token outputs:surface.connect = </mtl/BrownMat/BrownSrf.outputs:surface> | ||
|
||
def Shader "BrownSrf" | ||
{ | ||
uniform token info:id = "UsdPreviewSurface" | ||
color3f inputs:diffuseColor = (0.1293, 0.0625, 0.0050000004) | ||
token outputs:surface | ||
} | ||
} | ||
} | ||
|
||
def Scope "mtlFoliage" | ||
{ | ||
def Material "GreenMat" | ||
{ | ||
token outputs:surface.connect = </mtlFoliage/GreenMat/GreenSrf.outputs:surface> | ||
|
||
def Shader "GreenSrf" | ||
{ | ||
uniform token info:id = "UsdPreviewSurface" | ||
color3f inputs:diffuseColor = (0, 1, 0) | ||
token outputs:surface | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this file, please always compare with the function just above the new one to see the difference between cleaning up and renaming.