1# 2# Copyright 2017 Pixar 3# 4# Licensed under the Apache License, Version 2.0 (the "Apache License") 5# with the following modification; you may not use this file except in 6# compliance with the Apache License and the following modification to it: 7# Section 6. Trademarks. is deleted and replaced with: 8# 9# 6. Trademarks. This License does not grant permission to use the trade 10# names, trademarks, service marks, or product names of the Licensor 11# and its affiliates, except as required to comply with Section 4(c) of 12# the License and to reproduce the content of the NOTICE file. 13# 14# You may obtain a copy of the Apache License at 15# 16# http://www.apache.org/licenses/LICENSE-2.0 17# 18# Unless required by applicable law or agreed to in writing, software 19# distributed under the Apache License with the above modification is 20# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 21# KIND, either express or implied. See the Apache License for the specific 22# language governing permissions and limitations under the Apache License. 23# 24 25from pxr import Usd, UsdGeom, UsdShade 26from .qt import QtCore 27from .common import Timer, IncludedPurposes 28from pxr.UsdUtils.constantsGroup import ConstantsGroup 29 30class ChangeNotice(ConstantsGroup): 31 NONE = 0 32 RESYNC = 1 33 INFOCHANGES = 2 34 35class RootDataModel(QtCore.QObject): 36 """Data model providing centralized, moderated access to fundamental 37 information used throughout Usdview controllers, data models, and plugins. 38 """ 39 40 # Emitted when a new stage is set. 41 signalStageReplaced = QtCore.Signal() 42 signalPrimsChanged = QtCore.Signal(ChangeNotice, ChangeNotice) 43 44 def __init__(self, printTiming=False): 45 46 QtCore.QObject.__init__(self) 47 48 self._stage = None 49 self._printTiming = printTiming 50 51 self._currentFrame = Usd.TimeCode.Default() 52 self._playing = False 53 54 self._bboxCache = UsdGeom.BBoxCache(self._currentFrame, 55 [IncludedPurposes.DEFAULT, IncludedPurposes.PROXY], True) 56 self._xformCache = UsdGeom.XformCache(self._currentFrame) 57 58 self._pcListener = None 59 60 @property 61 def stage(self): 62 """Get the current Usd.Stage object.""" 63 64 return self._stage 65 66 @stage.setter 67 def stage(self, value): 68 """Sets the current Usd.Stage object, and emits a signal if it is 69 different from the previous stage. 70 """ 71 72 validStage = (value is None) or isinstance(value, Usd.Stage) 73 if not validStage: 74 raise ValueError("Expected USD Stage, got: {}".format(repr(value))) 75 76 if value is not self._stage: 77 78 if self._pcListener: 79 self._pcListener.Revoke() 80 self._pcListener = None 81 82 if value is None: 83 with Timer() as t: 84 self._stage = None 85 if self._printTiming: 86 t.PrintTime('close stage') 87 else: 88 self._stage = value 89 90 if self._stage: 91 from pxr import Tf 92 self._pcListener = \ 93 Tf.Notice.Register(Usd.Notice.ObjectsChanged, 94 self.__OnPrimsChanged, self._stage) 95 96 self.signalStageReplaced.emit() 97 98 def _emitPrimsChanged(self, primChange, propertyChange): 99 self.signalPrimsChanged.emit(primChange, propertyChange) 100 101 def __OnPrimsChanged(self, notice, sender): 102 primChange = ChangeNotice.NONE 103 propertyChange = ChangeNotice.NONE 104 105 for p in notice.GetResyncedPaths(): 106 if p.IsAbsoluteRootOrPrimPath(): 107 primChange = ChangeNotice.RESYNC 108 if p.IsPropertyPath(): 109 propertyChange = ChangeNotice.RESYNC 110 111 if primChange == ChangeNotice.NONE or propertyChange == ChangeNotice.NONE: 112 for p in notice.GetChangedInfoOnlyPaths(): 113 if p.IsPrimPath() and primChange == ChangeNotice.NONE: 114 primChange = ChangeNotice.INFOCHANGES 115 if p.IsPropertyPath() and propertyChange == ChangeNotice.NONE: 116 propertyChange = ChangeNotice.INFOCHANGES 117 118 self._emitPrimsChanged(primChange, propertyChange) 119 120 @property 121 def currentFrame(self): 122 """Get a Usd.TimeCode object which represents the current frame being 123 considered in Usdview.""" 124 125 return self._currentFrame 126 127 @currentFrame.setter 128 def currentFrame(self, value): 129 """Set the current frame to a new Usd.TimeCode object.""" 130 131 if not isinstance(value, Usd.TimeCode): 132 raise ValueError("Expected Usd.TimeCode, got: {}".format(value)) 133 134 self._currentFrame = value 135 self._bboxCache.SetTime(self._currentFrame) 136 self._xformCache.SetTime(self._currentFrame) 137 138 @property 139 def playing(self): 140 return self._playing 141 142 @playing.setter 143 def playing(self, value): 144 self._playing = value 145 146 # XXX This method should be removed after bug 114225 is resolved. Changes to 147 # the stage will then be used to trigger the caches to be cleared, so 148 # RootDataModel clients will not even need to know the caches exist. 149 def _clearCaches(self): 150 """Clears internal caches of bounding box and transform data. Should be 151 called when the current stage is changed in a way which affects this 152 data.""" 153 154 self._bboxCache.Clear() 155 self._xformCache.Clear() 156 157 @property 158 def useExtentsHint(self): 159 """Return True if bounding box calculations use extents hints from 160 prims. 161 """ 162 163 return self._bboxCache.GetUseExtentsHint() 164 165 @useExtentsHint.setter 166 def useExtentsHint(self, value): 167 """Set whether whether bounding box calculations should use extents 168 from prims. 169 """ 170 171 if not isinstance(value, bool): 172 raise ValueError("useExtentsHint must be of type bool.") 173 174 if value != self._bboxCache.GetUseExtentsHint(): 175 # Unfortunate that we must blow the entire BBoxCache, but we have no 176 # other alternative, currently. 177 purposes = self._bboxCache.GetIncludedPurposes() 178 self._bboxCache = UsdGeom.BBoxCache( 179 self._currentFrame, purposes, value) 180 181 @property 182 def includedPurposes(self): 183 """Get the set of included purposes used for bounding box calculations. 184 """ 185 186 return set(self._bboxCache.GetIncludedPurposes()) 187 188 @includedPurposes.setter 189 def includedPurposes(self, value): 190 """Set a new set of included purposes for bounding box calculations.""" 191 192 if not isinstance(value, set): 193 raise ValueError( 194 "Expected set of included purposes, got: {}".format( 195 repr(value))) 196 for purpose in value: 197 if purpose not in IncludedPurposes: 198 raise ValueError("Unknown included purpose: {}".format( 199 repr(purpose))) 200 201 self._bboxCache.SetIncludedPurposes(value) 202 203 def computeWorldBound(self, prim): 204 """Compute the world-space bounds of a prim.""" 205 206 if not isinstance(prim, Usd.Prim): 207 raise ValueError("Expected Usd.Prim object, got: {}".format( 208 repr(prim))) 209 210 return self._bboxCache.ComputeWorldBound(prim) 211 212 def getLocalToWorldTransform(self, prim): 213 """Compute the transformation matrix of a prim.""" 214 215 if not isinstance(prim, Usd.Prim): 216 raise ValueError("Expected Usd.Prim object, got: {}".format( 217 repr(prim))) 218 219 return self._xformCache.GetLocalToWorldTransform(prim) 220 221 def computeBoundMaterial(self, prim, purpose): 222 """Compute the material that the prim is bound to, for the given value 223 of material purpose. 224 """ 225 if not isinstance(prim, Usd.Prim): 226 raise ValueError("Expected Usd.Prim object, got: {}".format( 227 repr(prim))) 228 # We don't use the binding cache yet since it isn't exposed to python. 229 return UsdShade.MaterialBindingAPI( 230 prim).ComputeBoundMaterial(purpose) 231