1#!/pxrpythonsubst 2# 3# Copyright 2017 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, platform, sys, unittest 26from pxr import Ar,Sdf,Usd,Tf 27 28allFormats = ['usd' + x for x in 'ac'] 29 30class TestUsdNotices(unittest.TestCase): 31 def setUp(self): 32 self._ResetCounters() 33 34 def _ResetCounters(self): 35 self._changeCount = 0 36 self._contentsCount = 0 37 self._objectsCount = 0 38 self._editTargetsCount = 0 39 self._layerMutingCount = 0 40 41 def OnStageContentsChanged(self, *args): 42 self._changeCount += 1 43 self._contentsCount += 1 44 45 def OnObjectsChanged(self, *args): 46 self._changeCount += 1 47 self._objectsCount += 1 48 49 def OnStageEditTargetChanged(self, *args): 50 self._changeCount += 1 51 self._editTargetsCount += 1 52 53 def OnLayerMutingChanged(self, *args): 54 self._changeCount += 1 55 self._layerMutingCount += 1 56 57 def test_Basics(self): 58 contentsChanged = Tf.Notice.RegisterGlobally( 59 Usd.Notice.StageContentsChanged, 60 self.OnStageContentsChanged) 61 objectsChanged = Tf.Notice.RegisterGlobally( 62 Usd.Notice.ObjectsChanged, 63 self.OnObjectsChanged) 64 stageEditTargetChanged = Tf.Notice.RegisterGlobally( 65 Usd.Notice.StageEditTargetChanged, 66 self.OnStageEditTargetChanged) 67 layerMutingChanged = Tf.Notice.RegisterGlobally( 68 Usd.Notice.LayerMutingChanged, 69 self.OnLayerMutingChanged) 70 71 for fmt in allFormats: 72 self._ResetCounters() 73 s = Usd.Stage.CreateInMemory('Basics.'+fmt) 74 s.DefinePrim("/Foo") 75 self.assertEqual(self._changeCount, 2) 76 self.assertEqual(self._contentsCount, 1) 77 self.assertEqual(self._objectsCount, 1) 78 self.assertEqual(self._editTargetsCount, 0) 79 80 self._ResetCounters() 81 s.SetEditTarget(s.GetSessionLayer()) 82 self.assertEqual(self._changeCount, 1) 83 self.assertEqual(self._contentsCount, 0) 84 self.assertEqual(self._objectsCount, 0) 85 self.assertEqual(self._editTargetsCount, 1) 86 87 self._ResetCounters() 88 s.GetPrimAtPath("/Foo").SetMetadata("comment", "") 89 self.assertEqual(self._changeCount, 4) 90 self.assertEqual(self._contentsCount, 2) # Why 2? I expected 1. 91 self.assertEqual(self._objectsCount, 2) # We get an additional 92 # object resync notice when 93 # we first drop an over, in 94 # addition to the info-only 95 # change notice. 96 self.assertEqual(self._editTargetsCount, 0) 97 98 self._ResetCounters() 99 s.GetPrimAtPath("/Foo").SetMetadata("comment", "x") 100 # Now that the over(s) have been established, setting a value 101 # behaves as expected. 102 self.assertEqual(self._changeCount, 2) 103 self.assertEqual(self._contentsCount, 1) 104 self.assertEqual(self._objectsCount, 1) 105 self.assertEqual(self._editTargetsCount, 0) 106 107 self._ResetCounters() 108 s.MuteAndUnmuteLayers([s.GetSessionLayer().identifier], 109 [s.GetRootLayer().identifier]) 110 self.assertEqual(self._changeCount, 3) 111 self.assertEqual(self._objectsCount, 1) 112 self.assertEqual(self._contentsCount, 1) 113 self.assertEqual(self._layerMutingCount, 1) 114 115 # no notice should be called 116 self._ResetCounters() 117 with self.assertRaises(Tf.ErrorException): 118 s.MuteAndUnmuteLayers([s.GetRootLayer().identifier], 119 [s.GetRootLayer().identifier]) 120 self.assertEqual(self._changeCount, 0) 121 self.assertEqual(self._objectsCount, 0) 122 self.assertEqual(self._contentsCount, 0) 123 self.assertEqual(self._layerMutingCount, 0) 124 # testing for payload specific updates previously, load/unload calls 125 # didn't trigger notices 126 self._ResetCounters() 127 self.assertTrue(self._objectsCount == 0) 128 payloadBasedFile = 'payload_base.usda' 129 payloadBasedStage = Usd.Stage.Open(payloadBasedFile) 130 131 # the payload will be already loaded since we didn't supply 132 # the additional parameter to the stage constructor 133 payloadBasedStage.Unload('/Foo') 134 self.assertEqual(self._objectsCount, 1) 135 payloadBasedStage.Load('/Foo') 136 self.assertEqual(self._objectsCount, 2) 137 138 del contentsChanged 139 del objectsChanged 140 del stageEditTargetChanged 141 del layerMutingChanged 142 143 144 def test_ObjectsChangedNotice(self): 145 def OnResync(notice, stage): 146 self.assertEqual(notice.GetStage(), stage) 147 self.assertEqual(notice.GetResyncedPaths(), [Sdf.Path("/Foo")]) 148 self.assertEqual(notice.GetChangedInfoOnlyPaths(), []) 149 self.assertTrue(notice.AffectedObject(stage.GetPrimAtPath("/Foo"))) 150 self.assertTrue(notice.ResyncedObject(stage.GetPrimAtPath("/Foo"))) 151 self.assertTrue(not notice.ChangedInfoOnly(stage.GetPrimAtPath("/Foo"))) 152 153 def OnUpdate(notice, stage): 154 self.assertEqual(notice.GetStage(), stage) 155 self.assertEqual(notice.GetResyncedPaths(), []) 156 self.assertEqual(notice.GetChangedInfoOnlyPaths(), [Sdf.Path("/Foo")]) 157 self.assertTrue(notice.AffectedObject(stage.GetPrimAtPath("/Foo"))) 158 self.assertTrue(not notice.ResyncedObject(stage.GetPrimAtPath("/Foo"))) 159 self.assertTrue(notice.ChangedInfoOnly(stage.GetPrimAtPath("/Foo"))) 160 161 for fmt in allFormats: 162 self._ResetCounters() 163 s = Usd.Stage.CreateInMemory('ObjectsChangedNotice.'+fmt) 164 165 objectsChanged = Tf.Notice.Register(Usd.Notice.ObjectsChanged, 166 OnResync, s) 167 s.DefinePrim("/Foo") 168 169 objectsChanged = Tf.Notice.Register(Usd.Notice.ObjectsChanged, 170 OnUpdate, s) 171 s.GetPrimAtPath("/Foo").SetMetadata("comment", "") 172 del objectsChanged 173 174 def test_ObjectsChangedNoticeForAttributes(self): 175 for fmt in allFormats: 176 self._ResetCounters() 177 s = Usd.Stage.CreateInMemory('ObjectsChangedNoticeForProps.'+fmt) 178 prim = s.DefinePrim("/Foo") 179 180 def OnAttributeCreation(notice, stage): 181 self.assertEqual(notice.GetStage(), stage) 182 self.assertEqual(notice.GetResyncedPaths(), [Sdf.Path("/Foo.attr")]) 183 self.assertEqual(notice.GetChangedInfoOnlyPaths(), []) 184 self.assertTrue(notice.AffectedObject(stage.GetPrimAtPath("/Foo").GetAttribute("attr"))) 185 self.assertTrue(notice.ResyncedObject(stage.GetPrimAtPath("/Foo").GetAttribute("attr"))) 186 self.assertTrue(not notice.ChangedInfoOnly(stage.GetPrimAtPath("/Foo").GetAttribute("attr"))) 187 188 objectsChanged = Tf.Notice.Register( 189 Usd.Notice.ObjectsChanged, OnAttributeCreation, s) 190 attr = prim.CreateAttribute("attr", Sdf.ValueTypeNames.Int) 191 192 def OnAttributeValueChange(notice, stage): 193 self.assertEqual(notice.GetStage(), stage) 194 self.assertEqual(notice.GetResyncedPaths(), []) 195 self.assertTrue(notice.GetChangedInfoOnlyPaths() == \ 196 [Sdf.Path("/Foo.attr")]) 197 self.assertTrue(notice.AffectedObject( 198 stage.GetPrimAtPath("/Foo").GetAttribute("attr"))) 199 self.assertTrue(not notice.ResyncedObject( 200 stage.GetPrimAtPath("/Foo").GetAttribute("attr"))) 201 self.assertTrue(notice.ChangedInfoOnly( 202 stage.GetPrimAtPath("/Foo").GetAttribute("attr"))) 203 204 objectsChanged = Tf.Notice.Register( 205 Usd.Notice.ObjectsChanged, OnAttributeValueChange, s) 206 attr.Set(42) 207 208 del objectsChanged 209 210 def test_ObjectsChangedNoticeForRelationships(self): 211 for fmt in allFormats: 212 self._ResetCounters() 213 s = Usd.Stage.CreateInMemory('ObjectsChangedNoticeForRels.'+fmt) 214 prim = s.DefinePrim("/Foo") 215 216 def OnRelationshipCreation(notice, stage): 217 self.assertEqual(notice.GetStage(), stage) 218 self.assertEqual(notice.GetResyncedPaths(), [Sdf.Path("/Foo.rel")]) 219 self.assertEqual(notice.GetChangedInfoOnlyPaths(), []) 220 self.assertTrue(notice.AffectedObject( 221 stage.GetPrimAtPath("/Foo").GetRelationship("rel"))) 222 self.assertTrue(notice.ResyncedObject( 223 stage.GetPrimAtPath("/Foo").GetRelationship("rel"))) 224 self.assertTrue(not notice.ChangedInfoOnly( 225 stage.GetPrimAtPath("/Foo").GetRelationship("rel"))) 226 227 objectsChanged = Tf.Notice.Register( 228 Usd.Notice.ObjectsChanged, OnRelationshipCreation, s) 229 rel = prim.CreateRelationship("rel") 230 231 def OnRelationshipTargetChange(notice, stage): 232 self.assertEqual(notice.GetStage(), stage) 233 234 self.assertEqual(notice.GetResyncedPaths(), []) 235 self.assertEqual(notice.GetChangedInfoOnlyPaths(), 236 [Sdf.Path("/Foo.rel")]) 237 self.assertTrue(notice.AffectedObject( 238 stage.GetPrimAtPath("/Foo").GetRelationship("rel"))) 239 self.assertTrue(not notice.ResyncedObject( 240 stage.GetPrimAtPath("/Foo").GetRelationship("rel"))) 241 self.assertTrue(notice.ChangedInfoOnly( 242 stage.GetPrimAtPath("/Foo").GetRelationship("rel"))) 243 244 objectsChanged = Tf.Notice.Register( 245 Usd.Notice.ObjectsChanged, OnRelationshipTargetChange, s) 246 rel.AddTarget("/Bar") 247 248 del objectsChanged 249 250 def test_LayerMutingChange(self): 251 expectedResult = {} 252 def OnLayerMutingChange(notice, stage): 253 self.assertEqual(notice.GetStage(), stage) 254 self.assertEqual(notice.GetMutedLayers(), expectedResult["muted"]) 255 self.assertEqual(notice.GetUnmutedLayers(), 256 expectedResult["unmuted"]) 257 258 for fmt in allFormats: 259 s = Usd.Stage.CreateInMemory('LayerMutingChange.'+fmt) 260 261 # mute session layer and try unmute root layer 262 # note that root layer cannot be muted or unmuted 263 expectedResult = { 264 "muted": [s.GetSessionLayer().identifier], 265 "unmuted": [] 266 } 267 layerMutingChanged = Tf.Notice.Register( 268 Usd.Notice.LayerMutingChanged, 269 OnLayerMutingChange, s) 270 s.MuteAndUnmuteLayers([s.GetSessionLayer().identifier], 271 [s.GetRootLayer().identifier]) 272 273 # undo session layer muting and try to mute root layer 274 expectedResult = { 275 "muted": [], 276 "unmuted": [s.GetSessionLayer().identifier] 277 } 278 with self.assertRaises(Tf.ErrorException): 279 s.MuteAndUnmuteLayers([s.GetRootLayer().identifier], 280 [s.GetSessionLayer().identifier]) 281 282 # test to make sure callback is still called for muting of layers 283 # not part of the stage 284 layer1 = Sdf.Layer.CreateNew('nonStageLayer.'+fmt) 285 expectedResult = { 286 "muted": [layer1.identifier], 287 "unmuted": [] 288 } 289 s.MuteAndUnmuteLayers([layer1.identifier],[]) 290 291 del layerMutingChanged 292 293 def test_Reload(self): 294 s = Usd.Stage.CreateInMemory() 295 s.DefinePrim('/Root') 296 297 def OnReload(notice, sender): 298 self.assertEqual(notice.GetStage(), s) 299 self.assertEqual(notice.GetResyncedPaths(), ['/Root']) 300 self.assertFalse(notice.HasChangedFields('/Root')) 301 self.assertEqual(notice.GetChangedFields('/Root'), []) 302 303 # XXX: The pseudo-root is marked as having info changed 304 # because of the didReloadContent bit in the layer changelist. 305 # This doesn't seem correct. 306 self.assertEqual(notice.GetChangedInfoOnlyPaths(), ['/']) 307 self.assertFalse(notice.HasChangedFields('/')) 308 self.assertFalse(notice.GetChangedFields('/'), []) 309 310 notice = Tf.Notice.Register(Usd.Notice.ObjectsChanged, OnReload, s) 311 312 s.Reload() 313 314 @unittest.skipIf(platform.system() == "Windows" and 315 not hasattr(Ar.Resolver, "CreateIdentifier"), 316 "This test case currently fails on Windows due to " 317 "path canonicalization issues except with Ar 2.0.") 318 def test_InvalidLayerReloadChange(self): 319 s = Usd.Stage.CreateNew('LayerReloadChange.usda') 320 321 # Create a prim with a reference to a non-existent layer. 322 # This should result in a composition error. 323 prim = s.DefinePrim('/ModelReference') 324 prim.GetReferences().AddReference( 325 assetPath='./Model.usda', primPath='/Model') 326 327 s.Save() 328 329 # Create a new layer at the referenced path and reload the referencing 330 # stage. This should cause the layer to be opened and a resync for the 331 # referencing prim. 332 try: 333 layer = Sdf.Layer.CreateNew('Model.usda') 334 Sdf.CreatePrimInLayer(layer, '/Model') 335 336 class OnReload(object): 337 def __init__(self, stage, fixture): 338 super(OnReload, self).__init__() 339 self.receivedNotice = False 340 self.fixture = fixture 341 self.stage = stage 342 self._key = Tf.Notice.Register( 343 Usd.Notice.ObjectsChanged, self._HandleChange, stage) 344 345 def _HandleChange(self, notice, sender): 346 self.receivedNotice = True 347 self.fixture.assertEqual(notice.GetStage(), self.stage) 348 self.fixture.assertEqual(notice.GetResyncedPaths(), 349 [Sdf.Path('/ModelReference')]) 350 self.fixture.assertFalse( 351 notice.HasChangedFields('/ModelReference')) 352 self.fixture.assertEqual( 353 notice.GetChangedFields('/ModelReference'), []) 354 self.fixture.assertEqual(notice.GetChangedInfoOnlyPaths(), 355 []) 356 357 l = OnReload(s, self) 358 s.Reload() 359 360 # Should have received a resync notice and composed the referenced 361 # /Model prim into /ModelReference. 362 self.assertTrue(l.receivedNotice) 363 self.assertEqual( 364 s.GetPrimAtPath('/ModelReference').GetPrimStack()[1], 365 layer.GetPrimAtPath('/Model')) 366 367 finally: 368 # Make sure to remove Model.usda so subsequent test runs don't 369 # pick it up, otherwise test case won't work. 370 try: 371 os.remove('Model.usda') 372 except: 373 pass 374 375if __name__ == "__main__": 376 unittest.main() 377