1# 2# Copyright 2016 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# 24from __future__ import print_function 25 26from .qt import QtCore, QtGui, QtWidgets 27import os, time, sys, platform, math 28from pxr import Ar, Tf, Sdf, Kind, Usd, UsdGeom, UsdShade 29from .customAttributes import CustomAttribute 30from pxr.UsdUtils.constantsGroup import ConstantsGroup 31 32DEBUG_CLIPPING = "USDVIEWQ_DEBUG_CLIPPING" 33 34class ClearColors(ConstantsGroup): 35 """Names of available background colors.""" 36 BLACK = "Black" 37 DARK_GREY = "Grey (Dark)" 38 LIGHT_GREY = "Grey (Light)" 39 WHITE = "White" 40 41class DefaultFontFamily(ConstantsGroup): 42 """Names of the default font family and monospace font family to be used 43 with usdview""" 44 FONT_FAMILY = "Roboto" 45 MONOSPACE_FONT_FAMILY = "Roboto Mono" 46 47class HighlightColors(ConstantsGroup): 48 """Names of available highlight colors for selected objects.""" 49 WHITE = "White" 50 YELLOW = "Yellow" 51 CYAN = "Cyan" 52 53class UIBaseColors(ConstantsGroup): 54 RED = QtGui.QBrush(QtGui.QColor(230, 132, 131)) 55 LIGHT_SKY_BLUE = QtGui.QBrush(QtGui.QColor(135, 206, 250)) 56 DARK_YELLOW = QtGui.QBrush(QtGui.QColor(222, 158, 46)) 57 58class UIPrimTypeColors(ConstantsGroup): 59 HAS_ARCS = UIBaseColors.DARK_YELLOW 60 NORMAL = QtGui.QBrush(QtGui.QColor(227, 227, 227)) 61 INSTANCE = UIBaseColors.LIGHT_SKY_BLUE 62 PROTOTYPE = QtGui.QBrush(QtGui.QColor(118, 136, 217)) 63 64class UIPrimTreeColors(ConstantsGroup): 65 SELECTED = QtGui.QBrush(QtGui.QColor(189, 155, 84)) 66 SELECTED_HOVER = QtGui.QBrush(QtGui.QColor(227, 186, 101)) 67 ANCESTOR_OF_SELECTED = QtGui.QBrush(QtGui.QColor(189, 155, 84, 50)) 68 ANCESTOR_OF_SELECTED_HOVER = QtGui.QBrush(QtGui.QColor(189, 155, 84, 100)) 69 UNSELECTED_HOVER = QtGui.QBrush(QtGui.QColor(70, 70, 70)) 70 71class UIPropertyValueSourceColors(ConstantsGroup): 72 FALLBACK = UIBaseColors.DARK_YELLOW 73 TIME_SAMPLE = QtGui.QBrush(QtGui.QColor(177, 207, 153)) 74 DEFAULT = UIBaseColors.LIGHT_SKY_BLUE 75 NONE = QtGui.QBrush(QtGui.QColor(140, 140, 140)) 76 VALUE_CLIPS = QtGui.QBrush(QtGui.QColor(230, 150, 230)) 77 78class UIFonts(ConstantsGroup): 79 # Font constants. We use font in the prim browser to distinguish 80 # "resolved" prim specifier 81 # XXX - the use of weight here may need to be revised depending on font family 82 BASE_POINT_SIZE = 10 83 84 ITALIC = QtGui.QFont() 85 ITALIC.setWeight(QtGui.QFont.Light) 86 ITALIC.setItalic(True) 87 88 NORMAL = QtGui.QFont() 89 NORMAL.setWeight(QtGui.QFont.Normal) 90 91 BOLD = QtGui.QFont() 92 BOLD.setWeight(QtGui.QFont.Bold) 93 94 BOLD_ITALIC = QtGui.QFont() 95 BOLD_ITALIC.setWeight(QtGui.QFont.Bold) 96 BOLD_ITALIC.setItalic(True) 97 98 OVER_PRIM = ITALIC 99 DEFINED_PRIM = BOLD 100 ABSTRACT_PRIM = NORMAL 101 102 INHERITED = QtGui.QFont() 103 INHERITED.setPointSize(BASE_POINT_SIZE * 0.8) 104 INHERITED.setWeight(QtGui.QFont.Normal) 105 INHERITED.setItalic(True) 106 107class KeyboardShortcuts(ConstantsGroup): 108 FramingKey = QtCore.Qt.Key_F 109 110class PropertyViewIndex(ConstantsGroup): 111 TYPE, NAME, VALUE = range(3) 112 113ICON_DIR_ROOT = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons') 114 115# We use deferred loading because icons can't be constructed before 116# application initialization time. 117_icons = {} 118def _DeferredIconLoad(path): 119 fullPath = os.path.join(ICON_DIR_ROOT, path) 120 try: 121 icon = _icons[fullPath] 122 except KeyError: 123 icon = QtGui.QIcon(fullPath) 124 _icons[fullPath] = icon 125 return icon 126 127class PropertyViewIcons(ConstantsGroup): 128 ATTRIBUTE = lambda: _DeferredIconLoad('usd-attr-plain-icon.png') 129 ATTRIBUTE_WITH_CONNECTIONS = lambda: _DeferredIconLoad('usd-attr-with-conn-icon.png') 130 RELATIONSHIP = lambda: _DeferredIconLoad('usd-rel-plain-icon.png') 131 RELATIONSHIP_WITH_TARGETS = lambda: _DeferredIconLoad('usd-rel-with-target-icon.png') 132 TARGET = lambda: _DeferredIconLoad('usd-target-icon.png') 133 CONNECTION = lambda: _DeferredIconLoad('usd-conn-icon.png') 134 COMPOSED = lambda: _DeferredIconLoad('usd-cmp-icon.png') 135 136class PropertyViewDataRoles(ConstantsGroup): 137 ATTRIBUTE = "Attr" 138 RELATIONSHIP = "Rel" 139 ATTRIBUTE_WITH_CONNNECTIONS = "Attr_" 140 RELATIONSHIP_WITH_TARGETS = "Rel_" 141 TARGET = "Tgt" 142 CONNECTION = "Conn" 143 COMPOSED = "Cmp" 144 145class RenderModes(ConstantsGroup): 146 # Render modes 147 WIREFRAME = "Wireframe" 148 WIREFRAME_ON_SURFACE = "WireframeOnSurface" 149 SMOOTH_SHADED = "Smooth Shaded" 150 FLAT_SHADED = "Flat Shaded" 151 POINTS = "Points" 152 GEOM_ONLY = "Geom Only" 153 GEOM_FLAT = "Geom Flat" 154 GEOM_SMOOTH = "Geom Smooth" 155 HIDDEN_SURFACE_WIREFRAME = "Hidden Surface Wireframe" 156 157class ShadedRenderModes(ConstantsGroup): 158 # Render modes which use shading 159 SMOOTH_SHADED = RenderModes.SMOOTH_SHADED 160 FLAT_SHADED = RenderModes.FLAT_SHADED 161 WIREFRAME_ON_SURFACE = RenderModes.WIREFRAME_ON_SURFACE 162 GEOM_FLAT = RenderModes.GEOM_FLAT 163 GEOM_SMOOTH = RenderModes.GEOM_SMOOTH 164 165class ColorCorrectionModes(ConstantsGroup): 166 # Color correction used when render is presented to screen 167 # These strings should match HdxColorCorrectionTokens 168 DISABLED = "disabled" 169 SRGB = "sRGB" 170 OPENCOLORIO = "openColorIO" 171 172class PickModes(ConstantsGroup): 173 # Pick modes 174 PRIMS = "Prims" 175 MODELS = "Models" 176 INSTANCES = "Instances" 177 PROTOTYPES = "Prototypes" 178 179class SelectionHighlightModes(ConstantsGroup): 180 # Selection highlight modes 181 NEVER = "Never" 182 ONLY_WHEN_PAUSED = "Only when paused" 183 ALWAYS = "Always" 184 185class CameraMaskModes(ConstantsGroup): 186 NONE = "none" 187 PARTIAL = "partial" 188 FULL = "full" 189 190class IncludedPurposes(ConstantsGroup): 191 DEFAULT = UsdGeom.Tokens.default_ 192 PROXY = UsdGeom.Tokens.proxy 193 GUIDE = UsdGeom.Tokens.guide 194 RENDER = UsdGeom.Tokens.render 195 196def _PropTreeWidgetGetRole(tw): 197 return tw.data(PropertyViewIndex.TYPE, QtCore.Qt.ItemDataRole.WhatsThisRole) 198 199def PropTreeWidgetTypeIsRel(tw): 200 role = _PropTreeWidgetGetRole(tw) 201 return role in (PropertyViewDataRoles.RELATIONSHIP, 202 PropertyViewDataRoles.RELATIONSHIP_WITH_TARGETS) 203 204def _UpdateLabelText(text, substring, mode): 205 return text.replace(substring, '<'+mode+'>'+substring+'</'+mode+'>') 206 207def ItalicizeLabelText(text, substring): 208 return _UpdateLabelText(text, substring, 'i') 209 210def BoldenLabelText(text, substring): 211 return _UpdateLabelText(text, substring, 'b') 212 213def ColorizeLabelText(text, substring, r, g, b): 214 return _UpdateLabelText(text, substring, 215 "span style=\"color:rgb(%d, %d, %d);\"" % (r, g, b)) 216 217def PrintWarning(title, description): 218 msg = sys.stderr 219 print("------------------------------------------------------------", file=msg) 220 print("WARNING: %s" % title, file=msg) 221 print(description, file=msg) 222 print("------------------------------------------------------------", file=msg) 223 224def GetValueAndDisplayString(prop, time): 225 """If `prop` is a timeSampled Sdf.AttributeSpec, compute a string specifying 226 how many timeSamples it possesses. Otherwise, compute the single default 227 value, or targets for a relationship, or value at 'time' for a 228 Usd.Attribute. Return a tuple of a parameterless function that returns the 229 resolved value at 'time', and the computed brief string for display. We 230 return a value-producing function rather than the value itself because for 231 an Sdf.AttributeSpec with multiple timeSamples, the resolved value is 232 *all* of the timeSamples, which can be expensive to compute, and is 233 rarely needed. 234 """ 235 def _ValAndStr(val): 236 return (lambda: val, GetShortStringForValue(prop, val)) 237 238 if isinstance(prop, Usd.Relationship): 239 return _ValAndStr(prop.GetTargets()) 240 elif isinstance(prop, (Usd.Attribute, CustomAttribute)): 241 return _ValAndStr(prop.Get(time)) 242 elif isinstance(prop, Sdf.AttributeSpec): 243 if time == Usd.TimeCode.Default(): 244 return _ValAndStr(prop.default) 245 else: 246 numTimeSamples = prop.layer.GetNumTimeSamplesForPath(prop.path) 247 if numTimeSamples == 0: 248 return _ValAndStr(prop.default) 249 else: 250 def _GetAllTimeSamples(attrSpec): 251 l = attrSpec.layer 252 p = attrSpec.path 253 ordinates = l.ListTimeSamplesForPath(p) 254 return [(o, l.QueryTimeSample(p, o)) for o in ordinates] 255 256 if numTimeSamples == 1: 257 valStr = "1 time sample" 258 else: 259 valStr = str(numTimeSamples) + " time samples" 260 261 return (lambda prop=prop: _GetAllTimeSamples(prop), valStr) 262 263 elif isinstance(prop, Sdf.RelationshipSpec): 264 return _ValAndStr(prop.targetPathList) 265 266 return (lambda: None, "unrecognized property type") 267 268 269def GetShortStringForValue(prop, val): 270 if isinstance(prop, Usd.Relationship): 271 val = ", ".join(str(p) for p in val) 272 elif isinstance(prop, Sdf.RelationshipSpec): 273 return str(prop.targetPathList) 274 275 # If there is no value opinion, we do not want to display anything, 276 # since python 'None' has a different meaning than usda-authored None, 277 # which is how we encode attribute value blocks (which evaluate to 278 # Sdf.ValueBlock) 279 if val is None: 280 return '' 281 282 from .scalarTypes import GetScalarTypeFromAttr 283 scalarType, isArray = GetScalarTypeFromAttr(prop) 284 result = '' 285 if isArray and not isinstance(val, Sdf.ValueBlock): 286 def arrayToStr(a): 287 from itertools import chain 288 elems = a if len(a) <= 6 else chain(a[:3], ['...'], a[-3:]) 289 return '[' + ', '.join(map(str, elems)) + ']' 290 if val is not None and len(val): 291 result = "%s[%d]: %s" % (scalarType, len(val), arrayToStr(val)) 292 else: 293 result = "%s[]" % scalarType 294 else: 295 result = str(val) 296 297 return result[:500] 298 299# Return a string that reports size in metric units (units of 1000, not 1024). 300def ReportMetricSize(sizeInBytes): 301 if sizeInBytes == 0: 302 return "0 B" 303 sizeSuffixes = ("B", "KB", "MB", "GB", "TB", "PB", "EB") 304 i = int(math.floor(math.log(sizeInBytes, 1000))) 305 if i >= len(sizeSuffixes): 306 i = len(sizeSuffixes) - 1 307 p = math.pow(1000, i) 308 s = round(sizeInBytes / p, 2) 309 return "%s %s" % (s, sizeSuffixes[i]) 310 311# Return attribute status at a certian frame (is it using the default, or the 312# fallback? Is it authored at this frame? etc. 313def _GetAttributeStatus(attribute, frame): 314 return attribute.GetResolveInfo(frame).GetSource() 315 316# Return a Font corresponding to certain attribute properties. 317# Currently this only applies italicization on interpolated time samples. 318def GetPropertyTextFont(prop, frame): 319 if not isinstance(prop, Usd.Attribute): 320 # Early-out for non-attribute properties. 321 return None 322 323 frameVal = frame.GetValue() 324 bracketing = prop.GetBracketingTimeSamples(frameVal) 325 326 # Note that some attributes return an empty tuple, some None, from 327 # GetBracketingTimeSamples(), but all will be fed into this function. 328 if bracketing and (len(bracketing) == 2) and (bracketing[0] != frameVal): 329 return UIFonts.ITALIC 330 331 return None 332 333# Helper function that takes attribute status and returns the display color 334def GetPropertyColor(prop, frame, hasValue=None, hasAuthoredValue=None, 335 valueIsDefault=None): 336 if not isinstance(prop, Usd.Attribute): 337 # Early-out for non-attribute properties. 338 return UIBaseColors.RED.color() 339 340 statusToColor = {Usd.ResolveInfoSourceFallback : UIPropertyValueSourceColors.FALLBACK, 341 Usd.ResolveInfoSourceDefault : UIPropertyValueSourceColors.DEFAULT, 342 Usd.ResolveInfoSourceValueClips : UIPropertyValueSourceColors.VALUE_CLIPS, 343 Usd.ResolveInfoSourceTimeSamples: UIPropertyValueSourceColors.TIME_SAMPLE, 344 Usd.ResolveInfoSourceNone : UIPropertyValueSourceColors.NONE} 345 346 valueSource = _GetAttributeStatus(prop, frame) 347 348 return statusToColor[valueSource].color() 349 350# Gathers information about a layer used as a subLayer, including its 351# position in the layerStack hierarchy. 352class SubLayerInfo(object): 353 def __init__(self, sublayer, offset, containingLayer, prefix): 354 self.layer = sublayer 355 self.offset = offset 356 self.parentLayer = containingLayer 357 self._prefix = prefix 358 359 def GetOffsetString(self): 360 o = self.offset.offset 361 s = self.offset.scale 362 if o == 0: 363 if s == 1: 364 return "" 365 else: 366 return str.format("(scale = {})", s) 367 elif s == 1: 368 return str.format("(offset = {})", o) 369 else: 370 return str.format("(offset = {0}; scale = {1})", o, s) 371 372 def GetHierarchicalDisplayString(self): 373 return self._prefix + self.layer.GetDisplayName() 374 375def _AddSubLayers(layer, layerOffset, prefix, parentLayer, layers): 376 offsets = layer.subLayerOffsets 377 layers.append(SubLayerInfo(layer, layerOffset, parentLayer, prefix)) 378 for i, l in enumerate(layer.subLayerPaths): 379 offset = offsets[i] if offsets is not None and len(offsets) > i else Sdf.LayerOffset() 380 subLayer = Sdf.Layer.FindRelativeToLayer(layer, l) 381 # Due to an unfortunate behavior of the Pixar studio resolver, 382 # FindRelativeToLayer() may fail to resolve certain paths. We will 383 # remove this extra Find() call as soon as we can retire the behavior; 384 # in the meantime, the extra call does not hurt (but should not, in 385 # general, be necessary) 386 if not subLayer: 387 subLayer = Sdf.Layer.Find(l) 388 389 if subLayer: 390 # This gives a 'tree'-ish presentation, but it looks sad in 391 # a QTableWidget. Just use spaces for now 392 # addedPrefix = "|-- " if parentLayer is None else "| " 393 addedPrefix = " " 394 _AddSubLayers(subLayer, offset, addedPrefix + prefix, layer, layers) 395 else: 396 print("Could not find layer " + l) 397 398def GetRootLayerStackInfo(layer): 399 layers = [] 400 _AddSubLayers(layer, Sdf.LayerOffset(), "", None, layers) 401 return layers 402 403def PrettyFormatSize(sz): 404 k = 1024 405 meg = k * 1024 406 gig = meg * 1024 407 ter = gig * 1024 408 409 sz = float(sz) 410 if sz > ter: 411 return "%.1fT" % (sz/float(ter)) 412 elif sz > gig: 413 return "%.1fG" % (sz/float(gig)) 414 elif sz > meg: 415 return "%.1fM" % (sz/float(meg)) 416 elif sz > k: 417 return "%.1fK" % (sz/float(k)) 418 else: 419 return "%db" % sz 420 421 422class Timer(object): 423 """Use as a context object with python's "with" statement, like so: 424 with Timer() as t: 425 doSomeStuff() 426 t.PrintTime("did some stuff") 427 """ 428 def __enter__(self): 429 self._start = time.time() 430 self.interval = 0 431 return self 432 433 def __exit__(self, *args): 434 self._end = time.time() 435 self.interval = self._end - self._start 436 437 def PrintTime(self, action): 438 print("Time to %s: %2.3fs" % (action, self.interval)) 439 440 441class BusyContext(object): 442 """When used as a context object with python's "with" statement, 443 will set Qt's busy cursor upon entry and pop it on exit. 444 """ 445 def __enter__(self): 446 QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.BusyCursor) 447 448 def __exit__(self, *args): 449 QtWidgets.QApplication.restoreOverrideCursor() 450 451 452def InvisRootPrims(stage): 453 """Make all defined root prims of stage be invisible, 454 at Usd.TimeCode.Default()""" 455 for p in stage.GetPseudoRoot().GetChildren(): 456 UsdGeom.Imageable(p).MakeInvisible() 457 458def _RemoveVisibilityRecursive(primSpec): 459 try: 460 primSpec.RemoveProperty(primSpec.attributes[UsdGeom.Tokens.visibility]) 461 except IndexError: 462 pass 463 for child in primSpec.nameChildren: 464 _RemoveVisibilityRecursive(child) 465 466def ResetSessionVisibility(stage): 467 session = stage.GetSessionLayer() 468 with Sdf.ChangeBlock(): 469 _RemoveVisibilityRecursive(session.pseudoRoot) 470 471# This is unfortunate.. but until UsdAttribute will return you a ResolveInfo, 472# we have little alternative, other than manually walking prim's PcpPrimIndex 473def HasSessionVis(prim): 474 """Is there a session-layer override for visibility for 'prim'?""" 475 session = prim.GetStage().GetSessionLayer() 476 primSpec = session.GetPrimAtPath(prim.GetPath()) 477 return bool(primSpec and UsdGeom.Tokens.visibility in primSpec.attributes) 478 479# This should be codified on UsdModelAPI, but maybe after 117137 is addressed... 480def GetEnclosingModelPrim(prim): 481 """If 'prim' is inside/under a model of any kind, return the closest 482 such ancestor prim - If 'prim' has no model ancestor, return None""" 483 if prim: 484 prim = prim.GetParent() 485 while prim: 486 # We use Kind here instead of prim.IsModel because point instancer 487 # prototypes currently don't register as models in IsModel. See 488 # bug: http://bugzilla.pixar.com/show_bug.cgi?id=117137 489 if Kind.Registry.IsA(Usd.ModelAPI(prim).GetKind(), Kind.Tokens.model): 490 break 491 prim = prim.GetParent() 492 493 return prim 494 495def GetPrimLoadability(prim): 496 """Return a tuple of (isLoadable, isLoaded) for 'prim', according to 497 the following rules: 498 A prim is loadable if it is active, and either of the following are true: 499 * prim has a payload 500 * prim is a model group 501 The latter is useful because loading is recursive on a UsdStage, and it 502 is convenient to be able to (e.g.) load everything loadable in a set. 503 504 A prim 'isLoaded' only if there are no unloaded prims beneath it, i.e. 505 it is stating whether the prim is "fully loaded". This 506 is a debatable definition, but seems useful for usdview's purposes.""" 507 if not (prim.IsActive() and (prim.IsGroup() or prim.HasAuthoredPayloads())): 508 return (False, True) 509 # XXX Note that we are potentially traversing the entire stage here. 510 # If this becomes a performance issue, we can cast this query into C++, 511 # cache results, etc. 512 for p in Usd.PrimRange(prim, Usd.PrimIsActive): 513 if not p.IsLoaded(): 514 return (True, False) 515 return (True, True) 516 517def GetPrimsLoadability(prims): 518 """Follow the logic of GetPrimLoadability for each prim, combining 519 results so that isLoadable is the disjunction of all prims, and 520 isLoaded is the conjunction.""" 521 isLoadable = False 522 isLoaded = True 523 for prim in prims: 524 loadable, loaded = GetPrimLoadability(prim) 525 isLoadable = isLoadable or loadable 526 isLoaded = isLoaded and loaded 527 return (isLoadable, isLoaded) 528 529def GetFileOwner(path): 530 try: 531 if platform.system() == 'Windows': 532 # This only works if pywin32 is installed. 533 # Try "pip install pypiwin32". 534 import win32security as w32s 535 fs = w32s.GetFileSecurity(path, w32s.OWNER_SECURITY_INFORMATION) 536 sdo = fs.GetSecurityDescriptorOwner() 537 name, domain, use = w32.LookupAccountSid(None, sdo) 538 return "%s\\%s" % (domain, name) 539 else: 540 import pwd 541 return pwd.getpwuid(os.stat(path).st_uid).pw_name 542 except: 543 return "<unknown>" 544 545# In future when we have better introspection abilities in Usd core API, 546# we will change this function to accept a prim rather than a primStack. 547def GetAssetCreationTime(primStack, assetIdentifier): 548 """Finds the weakest layer in which assetInfo.identifier is set to 549 'assetIdentifier', and considers that an "asset-defining layer". 550 If assetInfo.identifier is not set in any layer, assumes the weakest 551 layer is the defining layer. We then retrieve the creation time for 552 the asset by stat'ing the defining layer's real path. 553 554 Returns a triple of strings: (fileDisplayName, creationTime, owner)""" 555 definingLayer = None 556 for spec in reversed(primStack): 557 if spec.HasInfo('assetInfo'): 558 identifier = spec.GetInfo('assetInfo').get('identifier') 559 if identifier and identifier.path == assetIdentifier.path: 560 definingLayer = spec.layer 561 break 562 if definingLayer: 563 definingFile = definingLayer.realPath 564 else: 565 definingFile = primStack[-1].layer.realPath 566 567 if Ar.IsPackageRelativePath(definingFile): 568 definingFile = Ar.SplitPackageRelativePathOuter(definingFile)[0] 569 570 if not definingFile: 571 displayName = (definingLayer.GetDisplayName() 572 if definingLayer and definingLayer.anonymous else 573 "<in-memory layer>") 574 creationTime = "<unknown>" 575 owner = "<unknown>" 576 else: 577 displayName = definingFile.split('/')[-1] 578 579 try: 580 creationTime = time.ctime(os.stat(definingFile).st_ctime) 581 except: 582 creationTime = "<unknown>" 583 584 owner = GetFileOwner(definingFile) 585 586 return (displayName, creationTime, owner) 587 588 589def DumpMallocTags(stage, contextStr): 590 if Tf.MallocTag.IsInitialized(): 591 callTree = Tf.MallocTag.GetCallTree() 592 memInMb = Tf.MallocTag.GetTotalBytes() / (1024.0 * 1024.0) 593 594 import os.path as path 595 import tempfile 596 layerName = path.basename(stage.GetRootLayer().identifier) 597 # CallTree.Report() gives us the most informative (and processable) 598 # form of output, but it only accepts a fileName argument. So we 599 # use NamedTemporaryFile just to get a filename. 600 statsFile = tempfile.NamedTemporaryFile(prefix=layerName+'.', 601 suffix='.mallocTag', 602 delete=False) 603 statsFile.close() 604 reportName = statsFile.name 605 callTree.Report(reportName) 606 print("Memory consumption of %s for %s is %d Mb" % (contextStr, 607 layerName, 608 memInMb)) 609 print("For detailed analysis, see " + reportName) 610 else: 611 print("Unable to accumulate memory usage since the Pxr MallocTag system was not initialized") 612 613def GetInstanceIdForIndex(prim, instanceIndex, time): 614 '''Attempt to find an authored Id value for the instance at index 615 'instanceIndex' at time 'time', on the given prim 'prim', which we access 616 as a UsdGeom.PointInstancer (whether it actually is or not, to provide 617 some dynamic duck-typing for custom instancer types that support Ids. 618 Returns 'None' if no ids attribute was found, or if instanceIndex is 619 outside the bounds of the ids array.''' 620 if not prim or instanceIndex < 0: 621 return None 622 ids = UsdGeom.PointInstancer(prim).GetIdsAttr().Get(time) 623 if not ids or instanceIndex >= len(ids): 624 return None 625 return ids[instanceIndex] 626 627def GetInstanceIndicesForIds(prim, instanceIds, time): 628 '''Attempt to find the instance indices of a list of authored instance IDs 629 for prim 'prim' at time 'time'. If the prim is not a PointInstancer or does 630 not have authored IDs, returns None. If any ID from 'instanceIds' does not 631 exist at the given time, its index is not added to the list (because it does 632 not have an index).''' 633 ids = UsdGeom.PointInstancer(prim).GetIdsAttr().Get(time) 634 if ids: 635 return [instanceIndex for instanceIndex, instanceId in enumerate(ids) 636 if instanceId in instanceIds] 637 else: 638 return None 639 640def Drange(start, stop, step): 641 '''Return a list whose first element is 'start' and the following elements 642 (if any) are 'start' plus increasing whole multiples of 'step', up to but 643 not greater than 'stop'. For example: 644 Drange(1, 3, 0.3) -> [1, 1.3, 1.6, 1.9, 2.2, 2.5, 2.8]''' 645 lst = [start] 646 n = 1 647 while start + n * step <= stop: 648 lst.append(start + n * step) 649 n += 1 650 return lst 651 652class PrimNotFoundException(Exception): 653 """Raised when a prim does not exist at a valid path.""" 654 def __init__(self, path): 655 super(PrimNotFoundException, self).__init__( 656 "Prim not found at path in stage: %s" % str(path)) 657 658class PropertyNotFoundException(Exception): 659 """Raised when a property does not exist at a valid path.""" 660 def __init__(self, path): 661 super(PropertyNotFoundException, self).__init__( 662 "Property not found at path in stage: %s" % str(path)) 663 664class FixableDoubleValidator(QtGui.QDoubleValidator): 665 """This class implements a fixup() method for QDoubleValidator 666 (see method for specific behavior). To work around the brokenness 667 of Pyside's fixup() wrapping, we allow the validator to directly 668 update its parent if it is a QLineEdit, from within fixup(). Thus 669 every QLineEdit must possess its own unique FixableDoubleValidator. 670 671 The fixup method we supply (which can be usefully called directly) 672 applies clamping and rounding to enforce the QDoubleValidator's 673 range and decimals settings.""" 674 675 def __init__(self, parent): 676 super(FixableDoubleValidator, self).__init__(parent) 677 678 self._lineEdit = parent if isinstance(parent, QtWidgets.QLineEdit) else None 679 680 def fixup(self, valStr): 681 # We implement this to fulfill the virtual for internal QLineEdit 682 # behavior, hoping that PySide will do the right thing, but it is 683 # useless to call from Python directly due to string immutability 684 try: 685 val = float(valStr) 686 val = max(val, self.bottom()) 687 val = min(val, self.top()) 688 val = round(val) 689 valStr = str(val) 690 if self._lineEdit: 691 self._lineEdit.setText(valStr) 692 except ValueError: 693 pass 694