1#!/pxrpythonsubst 2# 3# Copyright 2020 Pixar 4# 5# Licensed under the Apache License, Version 2.0 (the "Apache License") 6# with the following modification; you may not use this file except in 7# compliance with the Apache License and the following modification to it: 8# Section 6. Trademarks. is deleted and replaced with: 9# 10# 6. Trademarks. This License does not grant permission to use the trade 11# names, trademarks, service marks, or product names of the Licensor 12# and its affiliates, except as required to comply with Section 4(c) of 13# the License and to reproduce the content of the NOTICE file. 14# 15# You may obtain a copy of the Apache License at 16# 17# http://www.apache.org/licenses/LICENSE-2.0 18# 19# Unless required by applicable law or agreed to in writing, software 20# distributed under the Apache License with the above modification is 21# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 22# KIND, either express or implied. See the Apache License for the specific 23# language governing permissions and limitations under the Apache License. 24 25import os, unittest 26from pxr import Plug, Sdf, Usd, Vt, Tf 27 28# Helper for verifying change procesing related to fallback prim type metadata 29# changes. This is used to wrap a call that will change the layer metadata and 30# verifies that the ObjectsChanged notice is sent and whether the change caused 31# all prims to be resynced or not. 32class ChangeNoticeVerifier(object): 33 def __init__(self, testRunner, expectedResyncAll=False): 34 self.testRunner = testRunner 35 self.expectedResyncAll = expectedResyncAll 36 self.receivedNotice = False 37 def __enter__(self): 38 self._listener = Tf.Notice.RegisterGlobally( 39 'UsdNotice::ObjectsChanged', self._OnNotice) 40 return self 41 def __exit__(self, exc_type, exc_val, exc_tb): 42 self._listener.Revoke() 43 # Verify that we actually did receive a notice on exit, otherwise we'd 44 # end up with a false positive if no notice got sent 45 self.testRunner.assertTrue(self.receivedNotice) 46 47 def _OnNotice(self, notice, sender): 48 self.receivedNotice = True 49 # If we expected a resync all the root path will be in the notices 50 # resynced paths, otherwise it will be its changed info only paths. 51 if self.expectedResyncAll: 52 self.testRunner.assertEqual(notice.GetResyncedPaths(), 53 [Sdf.Path('/')]) 54 self.testRunner.assertEqual(notice.GetChangedInfoOnlyPaths(), []) 55 else : 56 self.testRunner.assertEqual(notice.GetResyncedPaths(), []) 57 self.testRunner.assertEqual(notice.GetChangedInfoOnlyPaths(), 58 [Sdf.Path('/')]) 59 60class TestUsdFallbackPrimTypes(unittest.TestCase): 61 62 @classmethod 63 def setUpClass(cls): 64 pr = Plug.Registry() 65 testPlugins = pr.RegisterPlugins(os.path.abspath("resources")) 66 assert len(testPlugins) == 1, \ 67 "Failed to load expected test plugin" 68 assert testPlugins[0].name == "testUsdFallbackPrimTypes", \ 69 "Failed to load expected test plugin" 70 71 cls.validType1 = Usd.SchemaRegistry.GetConcreteTypeFromSchemaTypeName( 72 "ValidType_1") 73 assert cls.validType1 74 cls.validType2 = Usd.SchemaRegistry.GetConcreteTypeFromSchemaTypeName( 75 "ValidType_2") 76 assert cls.validType2 77 78 def test_OpenLayerWithFallbackTypes(self): 79 stage = Usd.Stage.Open("WithFallback.usda") 80 self.assertTrue(stage) 81 82 def _VerifyFallbacksInStageMetdata(typeName, expectedFallbacksList): 83 fallbacks = stage.GetMetadataByDictKey("fallbackPrimTypes", typeName) 84 if expectedFallbacksList is None: 85 self.assertIsNone(fallbacks) 86 else: 87 self.assertEqual(fallbacks, Vt.TokenArray(expectedFallbacksList)) 88 89 emptyPrimDef = Usd.SchemaRegistry().GetEmptyPrimDefinition() 90 validType1PrimDef = Usd.SchemaRegistry().FindConcretePrimDefinition( 91 "ValidType_1") 92 validType2PrimDef = Usd.SchemaRegistry().FindConcretePrimDefinition( 93 "ValidType_2") 94 95 def _VerifyPrimDefsSame(primDef1, primDef2): 96 self.assertEqual(primDef1.GetAppliedAPISchemas(), 97 primDef2.GetAppliedAPISchemas()) 98 self.assertEqual(primDef1.GetPropertyNames(), 99 primDef2.GetPropertyNames()) 100 for propName in primDef1.GetPropertyNames(): 101 self.assertEqual(primDef1.GetSchemaPropertySpec(propName), 102 primDef2.GetSchemaPropertySpec(propName)) 103 self.assertEqual(primDef1.ListMetadataFields(), 104 primDef2.ListMetadataFields()) 105 for fieldName in primDef1.ListMetadataFields(): 106 self.assertEqual(primDef1.GetMetadata(fieldName), 107 primDef2.GetMetadata(fieldName)) 108 109 # ValidPrim_1 : Has no fallbacks defined in metadata but its type name 110 # is a valid schema so it doesn't matter. This is the most typical case. 111 prim = stage.GetPrimAtPath("/ValidPrim_1") 112 _VerifyFallbacksInStageMetdata("ValidType_1", None) 113 self.assertTrue(prim) 114 self.assertEqual(prim.GetTypeName(), "ValidType_1") 115 primTypeInfo = prim.GetPrimTypeInfo() 116 self.assertEqual(primTypeInfo.GetTypeName(), "ValidType_1") 117 self.assertEqual(primTypeInfo.GetSchemaType(), self.validType1) 118 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "ValidType_1") 119 self.assertTrue(prim.IsA(self.validType1)) 120 self.assertTrue(prim.IsA(Usd.Typed)) 121 _VerifyPrimDefsSame(prim.GetPrimDefinition(), validType1PrimDef) 122 123 # ValidPrim_2 : Has fallbacks defined in metadata but its type name 124 # is a valid schema, so it ignores fallbacks. 125 prim = stage.GetPrimAtPath("/ValidPrim_2") 126 _VerifyFallbacksInStageMetdata("ValidType_2", ["ValidType_1"]) 127 self.assertTrue(prim) 128 self.assertEqual(prim.GetTypeName(), "ValidType_2") 129 primTypeInfo = prim.GetPrimTypeInfo() 130 self.assertEqual(primTypeInfo.GetTypeName(), "ValidType_2") 131 self.assertEqual(primTypeInfo.GetSchemaType(), self.validType2) 132 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "ValidType_2") 133 self.assertTrue(prim.IsA(self.validType2)) 134 self.assertTrue(prim.IsA(Usd.Typed)) 135 _VerifyPrimDefsSame(prim.GetPrimDefinition(), validType2PrimDef) 136 137 # InvalidPrim_1 : Has no fallbacks defined in metadata and its type name 138 # is not a valid schema. This will have an invalid schema type. 139 prim = stage.GetPrimAtPath("/InvalidPrim_1") 140 _VerifyFallbacksInStageMetdata("InvalidType_1", None) 141 self.assertTrue(prim) 142 self.assertEqual(prim.GetTypeName(), "InvalidType_1") 143 primTypeInfo = prim.GetPrimTypeInfo() 144 self.assertEqual(primTypeInfo.GetTypeName(), "InvalidType_1") 145 self.assertEqual(primTypeInfo.GetSchemaType(), Tf.Type.Unknown) 146 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "") 147 self.assertFalse(prim.IsA(Usd.Typed)) 148 _VerifyPrimDefsSame(prim.GetPrimDefinition(), emptyPrimDef) 149 150 # InvalidPrim_2 : This prim's type is not a valid schema type, but the 151 # type has a single fallback defined in metadata which is a valid type. 152 # This prim's schema type will be this fallback type. 153 prim = stage.GetPrimAtPath("/InvalidPrim_2") 154 _VerifyFallbacksInStageMetdata("InvalidType_2", ["ValidType_2"]) 155 self.assertTrue(prim) 156 self.assertEqual(prim.GetTypeName(), "InvalidType_2") 157 primTypeInfo = prim.GetPrimTypeInfo() 158 self.assertEqual(primTypeInfo.GetTypeName(), "InvalidType_2") 159 self.assertEqual(primTypeInfo.GetSchemaType(), self.validType2) 160 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "ValidType_2") 161 self.assertTrue(prim.IsA(self.validType2)) 162 self.assertTrue(prim.IsA(Usd.Typed)) 163 _VerifyPrimDefsSame(prim.GetPrimDefinition(), validType2PrimDef) 164 165 # InvalidPrim_3 : This prim's type is not a valid schema type, but the 166 # type has two fallbacks defined in metadata which are both valid types. 167 # This prim's schema type will be the first fallback type in the list. 168 prim = stage.GetPrimAtPath("/InvalidPrim_3") 169 _VerifyFallbacksInStageMetdata("InvalidType_3", 170 ["ValidType_1", "ValidType_2"]) 171 self.assertTrue(prim) 172 self.assertEqual(prim.GetTypeName(), "InvalidType_3") 173 primTypeInfo = prim.GetPrimTypeInfo() 174 self.assertEqual(primTypeInfo.GetTypeName(), "InvalidType_3") 175 self.assertEqual(primTypeInfo.GetSchemaType(), self.validType1) 176 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "ValidType_1") 177 self.assertTrue(prim.IsA(self.validType1)) 178 self.assertTrue(prim.IsA(Usd.Typed)) 179 _VerifyPrimDefsSame(prim.GetPrimDefinition(), validType1PrimDef) 180 181 # InvalidPrim_4 : This prim's type is not a valid schema type, but the 182 # type has two fallbacks defined in metadata. The first fallback type 183 # is itself invalid, but second is a valid type. This prim's schema type 184 # will be the second fallback type in the list. 185 prim = stage.GetPrimAtPath("/InvalidPrim_4") 186 _VerifyFallbacksInStageMetdata("InvalidType_4", 187 ["InvalidType_3", "ValidType_2"]) 188 self.assertTrue(prim) 189 self.assertEqual(prim.GetTypeName(), "InvalidType_4") 190 primTypeInfo = prim.GetPrimTypeInfo() 191 self.assertEqual(primTypeInfo.GetTypeName(), "InvalidType_4") 192 self.assertEqual(primTypeInfo.GetSchemaType(), self.validType2) 193 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "ValidType_2") 194 self.assertTrue(prim.IsA(self.validType2)) 195 self.assertTrue(prim.IsA(Usd.Typed)) 196 _VerifyPrimDefsSame(prim.GetPrimDefinition(), validType2PrimDef) 197 198 # InvalidPrim_5 : This prim's type is not a valid schema type, but the 199 # type has two fallbacks defined in metadata. However, both of these 200 # types are also invalid types. This will have an invalid schema type. 201 prim = stage.GetPrimAtPath("/InvalidPrim_5") 202 _VerifyFallbacksInStageMetdata("InvalidType_5", 203 ["InvalidType_3", "InvalidType_2"]) 204 self.assertTrue(prim) 205 self.assertEqual(prim.GetTypeName(), "InvalidType_5") 206 primTypeInfo = prim.GetPrimTypeInfo() 207 self.assertEqual(primTypeInfo.GetTypeName(), "InvalidType_5") 208 self.assertEqual(primTypeInfo.GetSchemaType(), Tf.Type.Unknown) 209 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "") 210 self.assertFalse(prim.IsA(Usd.Typed)) 211 _VerifyPrimDefsSame(prim.GetPrimDefinition(), emptyPrimDef) 212 213 # InvalidPrim_WithAPISchemas_1 : Has no fallbacks defined in metadata 214 # and its type name is not a valid schema. This will have an invalid 215 # schema type, but the API schemas will still be applied 216 prim = stage.GetPrimAtPath("/InvalidPrim_WithAPISchemas_1") 217 _VerifyFallbacksInStageMetdata("InvalidType_1", None) 218 self.assertTrue(prim) 219 self.assertEqual(prim.GetTypeName(), "InvalidType_1") 220 primTypeInfo = prim.GetPrimTypeInfo() 221 self.assertEqual(primTypeInfo.GetTypeName(), "InvalidType_1") 222 self.assertEqual(primTypeInfo.GetSchemaType(), Tf.Type.Unknown) 223 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "") 224 self.assertEqual(primTypeInfo.GetAppliedAPISchemas(), 225 ["CollectionAPI:foo"]) 226 self.assertFalse(prim.IsA(Usd.Typed)) 227 self.assertEqual(prim.GetAppliedSchemas(), ["CollectionAPI:foo"]) 228 229 # Create a new prim with an empty typename and the same API schemas 230 # as the above. Verify that because these two prims have the same 231 # effective schema type and applied schemas, that they use the same 232 # prim definition even though their type names differ. 233 otherPrim = stage.DefinePrim("/EmptyWithCollection", "") 234 Usd.CollectionAPI.Apply(otherPrim, "foo") 235 otherPrimTypeInfo = otherPrim.GetPrimTypeInfo() 236 self.assertNotEqual(otherPrim.GetTypeName(), prim.GetTypeName()) 237 self.assertNotEqual(otherPrimTypeInfo, primTypeInfo) 238 self.assertEqual(otherPrimTypeInfo.GetSchemaTypeName(), 239 primTypeInfo.GetSchemaTypeName()) 240 self.assertEqual(otherPrimTypeInfo.GetSchemaType(), 241 primTypeInfo.GetSchemaType()) 242 self.assertEqual(otherPrim.GetAppliedSchemas(), 243 prim.GetAppliedSchemas()) 244 _VerifyPrimDefsSame(otherPrim.GetPrimDefinition(), 245 prim.GetPrimDefinition()) 246 247 # InvalidPrim_2 : This prim's type is not a valid schema type, but the 248 # type has a single fallback defined in metadata which is a valid type. 249 # This prim's schema type will be this fallback type and the API schemas 250 # will be applied over the fallbacktype. 251 prim = stage.GetPrimAtPath("/InvalidPrim_WithAPISchemas_2") 252 _VerifyFallbacksInStageMetdata("InvalidType_2", ["ValidType_2"]) 253 self.assertTrue(prim) 254 self.assertEqual(prim.GetTypeName(), "InvalidType_2") 255 primTypeInfo = prim.GetPrimTypeInfo() 256 self.assertEqual(primTypeInfo.GetTypeName(), "InvalidType_2") 257 self.assertEqual(primTypeInfo.GetSchemaType(), self.validType2) 258 self.assertEqual(primTypeInfo.GetSchemaTypeName(), "ValidType_2") 259 self.assertEqual(primTypeInfo.GetAppliedAPISchemas(), 260 ["CollectionAPI:foo"]) 261 self.assertTrue(prim.IsA(self.validType2)) 262 self.assertTrue(prim.IsA(Usd.Typed)) 263 self.assertEqual(prim.GetAppliedSchemas(), ["CollectionAPI:foo"]) 264 265 # Create a new prim using the fallback typename and the same API schemas 266 # as the above. Verify that because these two prims have the same 267 # effective schema type and applied schemas, that they use the same 268 # prim definition even though their type names differ. 269 otherPrim = stage.DefinePrim("/Valid2WithCollection", "ValidType_2") 270 Usd.CollectionAPI.Apply(otherPrim, "foo") 271 otherPrimTypeInfo = otherPrim.GetPrimTypeInfo() 272 self.assertNotEqual(otherPrim.GetTypeName(), prim.GetTypeName()) 273 self.assertNotEqual(otherPrimTypeInfo, primTypeInfo) 274 self.assertEqual(otherPrimTypeInfo.GetSchemaTypeName(), 275 primTypeInfo.GetSchemaTypeName()) 276 self.assertEqual(otherPrimTypeInfo.GetSchemaType(), 277 primTypeInfo.GetSchemaType()) 278 self.assertEqual(otherPrim.GetAppliedSchemas(), 279 prim.GetAppliedSchemas()) 280 _VerifyPrimDefsSame(otherPrim.GetPrimDefinition(), 281 prim.GetPrimDefinition()) 282 283 def test_Sublayer(self): 284 # Create a new layer with same layer above as a sublayer. We don't 285 # compose sublayer metadata in stage level metadata so we don't get 286 # the fallback types like in the above test case. 287 layer = Sdf.Layer.CreateAnonymous() 288 layer.subLayerPaths.append('WithFallback.usda') 289 stage = Usd.Stage.Open(layer) 290 291 # The two prims with valid schema type names are still valid types. 292 prim = stage.GetPrimAtPath("/ValidPrim_1") 293 self.assertTrue(prim) 294 self.assertEqual(prim.GetTypeName(), "ValidType_1") 295 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaType(), 296 self.validType1) 297 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaTypeName(), 298 "ValidType_1") 299 300 prim = stage.GetPrimAtPath("/ValidPrim_2") 301 self.assertTrue(prim) 302 self.assertEqual(prim.GetTypeName(), "ValidType_2") 303 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaType(), 304 self.validType2) 305 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaTypeName(), 306 "ValidType_2") 307 308 # All of the prims with invalid types have no schema type since we 309 # have no fallbacks from the sublayer. 310 for primPath, typeName in [("/InvalidPrim_1", "InvalidType_1"), 311 ("/InvalidPrim_2", "InvalidType_2"), 312 ("/InvalidPrim_3", "InvalidType_3"), 313 ("/InvalidPrim_4", "InvalidType_4"), 314 ("/InvalidPrim_5", "InvalidType_5")] : 315 prim = stage.GetPrimAtPath(primPath) 316 self.assertTrue(prim) 317 self.assertEqual(prim.GetTypeName(), typeName) 318 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaType(), 319 Tf.Type.Unknown) 320 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaTypeName(), "") 321 322 def test_FallbackAuthoring(self): 323 # Test the manual authoring of fallback type metadata on the stage. 324 stage = Usd.Stage.Open("WithFallback.usda") 325 self.assertTrue(stage) 326 327 # InvalidPrim_1 : Has no fallbacks defined in metadata and its type name 328 # is not a valid schema. This will have an invalid schema type. 329 prim = stage.GetPrimAtPath("/InvalidPrim_1") 330 self.assertTrue(prim) 331 self.assertEqual(prim.GetTypeName(), "InvalidType_1") 332 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaType(), 333 Tf.Type.Unknown) 334 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaTypeName(), "") 335 336 # Author a fallback value for InvalidType_1 in the stage's 337 # fallbackPrimTypes metadata dictionary. This will resync all prims 338 # on the stage. 339 with ChangeNoticeVerifier(self, expectedResyncAll=True): 340 stage.SetMetadataByDictKey("fallbackPrimTypes", "InvalidType_1", 341 Vt.TokenArray(["ValidType_2"])) 342 343 # InvalidPrim_1 now has a schema type from the fallback type. 344 self.assertEqual(prim.GetTypeName(), "InvalidType_1") 345 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaType(), 346 self.validType2) 347 self.assertEqual(prim.GetPrimTypeInfo().GetSchemaTypeName(), 348 "ValidType_2") 349 350 def test_WritingSchemaFallbacks(self): 351 # Test the writing of fallback types defined in the schema through the 352 # stage API. 353 354 # Our test schema plugin has some fallbacks defined. Verify that we 355 # can get these as dictionary from the schema registry 356 schemaFallbacks = { 357 "ValidType_1" : Vt.TokenArray(["FallbackType_1"]), 358 "ValidType_2" : Vt.TokenArray(["FallbackType_2", "FallbackType_1"]) 359 } 360 self.assertEqual(Usd.SchemaRegistry().GetFallbackPrimTypes(), 361 schemaFallbacks) 362 363 # Create a new empty layer and stage from that layer. There will be 364 # no fallbackPrimTypes set on the newly opened stage 365 layer = Sdf.Layer.CreateNew("tempNew.usda") 366 self.assertTrue(layer) 367 stage = Usd.Stage.Open(layer) 368 self.assertTrue(stage) 369 self.assertFalse(stage.GetMetadata("fallbackPrimTypes")) 370 self.assertFalse(layer.GetPrimAtPath('/').GetInfo('fallbackPrimTypes')) 371 372 # Create a prim spec on the layer and save the layer itself. There will 373 # be no fallback metadata on the stage since we saved through the 374 # layer's API. 375 spec = Sdf.PrimSpec(layer, "ValidPrim", Sdf.SpecifierDef, "ValidType_2") 376 self.assertTrue(stage.GetPrimAtPath("/ValidPrim")) 377 layer.Save() 378 self.assertFalse(stage.GetMetadata("fallbackPrimTypes")) 379 self.assertFalse(layer.GetPrimAtPath('/').GetInfo('fallbackPrimTypes')) 380 381 # Now write the schema fallbacks to the layer using the stage API. This 382 # will write the schema fallbacks dictionary to the root layer. Verify 383 # that this layer change does not a cause a full stage resync. 384 with ChangeNoticeVerifier(self, expectedResyncAll=False): 385 stage.WriteFallbackPrimTypes() 386 self.assertEqual(stage.GetMetadata("fallbackPrimTypes"), 387 schemaFallbacks) 388 self.assertEqual(layer.GetPrimAtPath('/').GetInfo('fallbackPrimTypes'), 389 schemaFallbacks) 390 391 # Now manually author a different dictionary of fallback to the root 392 # layer. This will trigger a full stage resync and changes the stage's 393 # fallback types. 394 newFallbacks = { 395 "ValidType_1" : Vt.TokenArray(["ValidType_2, FallbackType_2"]), 396 "InValidType_1" : Vt.TokenArray(["ValidType_1"]) 397 } 398 with ChangeNoticeVerifier(self, expectedResyncAll=True): 399 layer.GetPrimAtPath('/').SetInfo('fallbackPrimTypes', 400 newFallbacks) 401 self.assertEqual(stage.GetMetadata("fallbackPrimTypes"), 402 newFallbacks) 403 self.assertEqual(layer.GetPrimAtPath('/').GetInfo('fallbackPrimTypes'), 404 newFallbacks) 405 406 # Write the schema fallbacks again and verify that it will add fallback 407 # types to the metadata dictionary from the schema registry but only for 408 # types that aren't already in the dictionary. 409 with ChangeNoticeVerifier(self, expectedResyncAll=False): 410 stage.WriteFallbackPrimTypes() 411 postSaveFallbacks = { 412 "ValidType_1" : Vt.TokenArray(["ValidType_2, FallbackType_2"]), 413 "ValidType_2" : Vt.TokenArray(["FallbackType_2", "FallbackType_1"]), 414 "InValidType_1" : Vt.TokenArray(["ValidType_1"]) 415 } 416 self.assertEqual(stage.GetMetadata("fallbackPrimTypes"), 417 postSaveFallbacks) 418 self.assertEqual(layer.GetPrimAtPath('/').GetInfo('fallbackPrimTypes'), 419 postSaveFallbacks) 420 421 422if __name__ == "__main__": 423 unittest.main() 424