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 collections import OrderedDict 26 27from pxr import Sdf, Gf 28from .qt import QtCore 29 30from .customAttributes import (ComputedPropertyNames, BoundingBoxAttribute, 31 LocalToWorldXformAttribute, ComputedPropertyFactory) 32 33 34# Indicates that all instances of a prim are selected. 35ALL_INSTANCES = -1 36 37 38class Blocker: 39 """Object which can be used to temporarily block the execution of a body of 40 code. This object is a context manager, and enters a 'blocked' state when 41 used in a 'with' statement. The 'blocked()' method can be used to find if 42 the Blocker is in this 'blocked' state. 43 """ 44 45 def __init__(self, exitCallback=lambda: None): 46 47 # A count is used rather than a 'blocked' flag to allow for nested 48 # blocking. 49 self._count = 0 50 51 # Fired when the Blocker's context is exited. 52 self._exitCallback = exitCallback 53 54 def __enter__(self): 55 """Enter the 'blocked' state until the context is exited.""" 56 57 self._count += 1 58 59 def __exit__(self, *args): 60 """Exit the 'blocked' state.""" 61 62 self._count -= 1 63 64 if not self.blocked(): 65 self._exitCallback() 66 67 def blocked(self): 68 """Returns True if in the 'blocked' state, and False otherwise.""" 69 70 return self._count > 0 71 72 73class _PrimSelection(object): 74 """This class keeps track of the core data for prim selection: paths and 75 instances. The methods here can be called in any order required without 76 corrupting the path selection state. 77 """ 78 79 def __init__(self): 80 81 # The order of paths selected needs to be maintained so we can track the 82 # focus path. An OrderedDict is more efficient than a list here since it 83 # supports efficient removal of arbitrary paths but still maintains the 84 # path order. Sdf.Path objects are the keys in the OrderedDict, and a 85 # set of selected instances are the values. If all instances are 86 # selected, None is the value rather than a set. 87 self._selection = OrderedDict() 88 89 self._added = set() 90 self._removed = set() 91 92 def _clearPrimPath(self, path): 93 """Clears a path from the selection and updates the diff.""" 94 95 if path in self._added: 96 # Path was added in this diff, but we are removing it again. Since 97 # there is no net change, it shouldn't be in _added or _removed. 98 self._added.discard(path) 99 else: 100 self._removed.add(path) 101 102 del self._selection[path] 103 104 def _discardInstance(self, path, instance): 105 """Discards an instance from the selection, then deletes the path from 106 the selection if it has no more instances. 107 """ 108 109 instances = self._selection[path] 110 instances.discard(instance) 111 if len(instances) == 0: 112 # Last instance deselected, so path should be deselected. 113 self._clearPrimPath(path) 114 115 def _allInstancesSelected(self, path): 116 """Returns True if all instances of a specified path are selected and 117 False otherwise. 118 """ 119 120 if path in self._selection: 121 return self._selection[path] == ALL_INSTANCES 122 else: 123 return False 124 125 def _noInstancesSelected(self, path): 126 """Returns True if all instances of a specified path are selected and 127 False otherwise. 128 """ 129 130 return path not in self._selection 131 132 def clear(self): 133 """Clear the path selection.""" 134 135 # _clearPrimPath modifies self._selection, so make a copy of keys 136 # before mutating it. 137 for path in list(self._selection.keys()): 138 self._clearPrimPath(path) 139 140 def removeMatchingPaths(self, matches): 141 """Remove any paths that pass the given predicate""" 142 143 # _clearPrimPath modifies self._selection, so make a copy of keys 144 # before mutating it. 145 for path in list(self._selection.keys()): 146 if matches(path): 147 self._clearPrimPath(path) 148 149 def addPrimPath(self, path, instance=ALL_INSTANCES): 150 """Add a path to the selection. If an instance is given, then only add 151 that instance. If all instances are selected when this happens then the 152 single instance will become the only selected one. 153 """ 154 155 # If the path is not already in the selection, update the diff. 156 if path not in self._selection: 157 if path in self._removed: 158 # Path was removed in this diff, but is back now. Since there is 159 # no net change, it shouldn't be in _added or _removed. 160 self._removed.discard(path) 161 else: 162 self._added.add(path) 163 164 if instance == ALL_INSTANCES: 165 # Trying to add all instances, make sure all instances are selected. 166 self._selection[path] = ALL_INSTANCES 167 else: 168 # Trying to add a single instance. 169 if self._allInstancesSelected(path) or self._noInstancesSelected(path): 170 # Either all instances selected or none selected. Create an 171 # empty set of instances then add the target instance. 172 self._selection[path] = set() 173 self._selection[path].add(instance) 174 175 def removePrimPath(self, path, instance=ALL_INSTANCES): 176 """Remove a path from the selection. If an instance is given, then only 177 remove that instance. If all instances are selected when this happens, 178 deselect all instances. If the target does not exist in the selection, 179 do nothing. 180 """ 181 182 if path in self._selection: 183 if instance == ALL_INSTANCES or self._allInstancesSelected(path): 184 # Want to deselect all instances or all instances are selected. 185 # Either way, deselect all. 186 self._clearPrimPath(path) 187 else: 188 # Some instances selected and want to deselect one of them. 189 self._discardInstance(path, instance) 190 191 def togglePrimPath(self, path, instance=ALL_INSTANCES): 192 """Toggle the selection of a path. If an instance is given, only toggle 193 that instance's selection. 194 """ 195 196 if path not in self._selection: 197 self.addPrimPath(path, instance) 198 return 199 200 # Path is at least partially selected. 201 if instance == ALL_INSTANCES: 202 # Trying to toggle all instances. 203 if self._allInstancesSelected(path): 204 # All instances are already selected, so deselect the path. 205 self._clearPrimPath(path) 206 else: 207 # Only some instances are selected, select all all of them. 208 self._selection[path] = ALL_INSTANCES 209 else: 210 # Trying to toggle a single instance. 211 if self._allInstancesSelected(path): 212 # Currently all instances are selected. Switch selection to 213 # only the new instance. 214 self._selection[path] = set([instance]) 215 else: 216 # Some instances already selected. Toggle the new instance 217 # in the selection. 218 instances = self._selection[path] 219 if instance in instances: 220 self._discardInstance(path, instance) 221 else: 222 instances.add(instance) 223 224 def getPrimPaths(self): 225 """Get a list of paths that are at least partially selected.""" 226 227 return list(self._selection.keys()) 228 229 def getPrimPathInstances(self): 230 """Get the full selection of paths and their corresponding selected 231 instances. 232 """ 233 234 return OrderedDict( 235 (path, set(instances)) if isinstance(instances, set) else (path, instances) 236 for path, instances in self._selection.items()) 237 238 def getDiff(self): 239 """Get the prims added to or removed from the selection since the last 240 time getDiff() was called. 241 """ 242 243 diff = (self._added, self._removed) 244 245 self._added = set() 246 self._removed = set() 247 248 return diff 249 250 251class _PropSelection(object): 252 """This class keeps track of the state of property selection.""" 253 254 def __init__(self): 255 256 self._selection = OrderedDict() 257 258 def clear(self): 259 """Clears the property selection.""" 260 261 self._selection = OrderedDict() 262 263 def addPropPath(self, primPath, propName): 264 """Add a property to the selection.""" 265 266 propTuple = (primPath, propName) 267 268 # If this property is already selected, remove it from the selection and 269 # re-add it so it becomes the focus property. 270 if propTuple in self._selection: 271 targets = self._selection[propTuple] 272 del self._selection[propTuple] 273 self._selection[propTuple] = targets 274 else: 275 self._selection[propTuple] = set() 276 277 def removePropPath(self, primPath, propName): 278 """Remove a property from the selection.""" 279 280 propTuple = (primPath, propName) 281 if propTuple in self._selection: 282 del self._selection[propTuple] 283 284 def addTarget(self, primPath, propName, target): 285 """Add a target to the selection. Also add the target's property if it 286 is not already in the selection. 287 """ 288 289 propTuple = (primPath, propName) 290 291 # If this property is already selected, remove it from the selection and 292 # re-add it so it becomes the focus property. 293 if propTuple in self._selection: 294 targets = self._selection[propTuple] 295 del self._selection[propTuple] 296 self._selection[propTuple] = targets 297 else: 298 targets = self._selection.setdefault(propTuple, set()) 299 300 targets.add(target) 301 302 def removeTarget(self, primPath, propName, target): 303 """Remove a target from the selection. If the target or its property are 304 not already in the selection, nothing is changed. 305 """ 306 307 propTuple = (primPath, propName) 308 if propTuple in self._selection: 309 self._selection[propTuple].discard(target) 310 311 def getPropPaths(self): 312 """Get the list of properties.""" 313 314 return list(self._selection.keys()) 315 316 def getTargets(self): 317 """Get a dictionary which maps selected properties to a set of their 318 selected targets or connections. 319 """ 320 321 propTargets = OrderedDict() 322 for propTuple, targets in self._selection.items(): 323 propTargets[propTuple] = set(targets) 324 325 return propTargets 326 327 328class SelectionDataModel(QtCore.QObject): 329 """Data model managing the current selection of prims and properties. 330 Please note that the owner of an instance of this class is 331 responsible for calling SelectionDataModel.removeUnpopulatedPrims() when 332 appropriate, lest methods like getPrims() return invalid prims.""" 333 334 # Signals must be declared in the class, but each instance of the selection 335 # data model has its own unique signal instances. 336 337 # When emitted, includes two sets: one of newly selected prims, and one of 338 # newly deselected prims. 339 signalPrimSelectionChanged = QtCore.Signal(set, set) 340 341 signalPropSelectionChanged = QtCore.Signal() 342 signalComputedPropSelectionChanged = QtCore.Signal() 343 344 def __init__(self, rootDataModel, _computedPropFactory=None): 345 QtCore.QObject.__init__(self) 346 347 self._rootDataModel = rootDataModel 348 349 # _computedPropFactory may be passed explicitly for unit testing. 350 if _computedPropFactory is None: 351 self._computedPropFactory = ComputedPropertyFactory( 352 self._rootDataModel) 353 else: 354 self._computedPropFactory = _computedPropFactory 355 356 self.batchPrimChanges = Blocker( 357 exitCallback=self._primSelectionChanged) 358 self.batchPropChanges = Blocker( 359 exitCallback=self._propSelectionChanged) 360 self.batchComputedPropChanges = Blocker( 361 exitCallback=self._computedPropSelectionChanged) 362 363 self._pointSelection = Gf.Vec3f(0.0, 0.0, 0.0) 364 365 self._primSelection = _PrimSelection() 366 # The path selection should never be empty. If it ever is, we 367 # immediately add the root path before returning to user control (see 368 # _primSelectionChanged). 369 self._primSelection.addPrimPath(Sdf.Path.absoluteRootPath) 370 # Clear the prim selection diff so we don't get the absolute root in the 371 # first signal from signalPrimSelectionChanged. 372 self._primSelection.getDiff() 373 374 self._lcdPathSelection = [Sdf.Path.absoluteRootPath] 375 376 self._propSelection = _PropSelection() 377 self._computedPropSelection = _PropSelection() 378 379 ### Internal Operations ### 380 def _primSelectionChanged(self, emitSelChangedSignal=True): 381 """Should be called whenever a change is made to _primSelection. Some 382 final work is done then the prim selection changed signal is emitted. 383 """ 384 385 # If updates are suppressed, do not emit a signal or do any 386 # pre-processing. 387 if self.batchPrimChanges.blocked(): 388 return 389 390 # Make sure there is always at least one path selected. 391 if len(self.getPrimPaths()) == 0: 392 self._primSelection.addPrimPath(Sdf.Path.absoluteRootPath) 393 394 # Recalculate the LCD prims whenever the path selection changes. 395 paths = self._primSelection.getPrimPaths() 396 if len(paths) > 1: 397 paths = [path for path in paths 398 if path != Sdf.Path.absoluteRootPath] 399 self._lcdPathSelection = Sdf.Path.RemoveDescendentPaths(paths) 400 401 # Finally, emit the changed signal. 402 added, removed = self._primSelection.getDiff() 403 if emitSelChangedSignal: 404 self.signalPrimSelectionChanged.emit(added, removed) 405 406 def _propSelectionChanged(self): 407 """Should be called whenever a change is made to _propSelection.""" 408 409 # If updates are suppressed, do not emit a signal or do any 410 # pre-processing. 411 if self.batchPropChanges.blocked(): 412 return 413 414 self.signalPropSelectionChanged.emit() 415 416 def _computedPropSelectionChanged(self): 417 """Should be called whenever a change is made to _computedPropSelection. 418 """ 419 420 # If updates are suppressed, do not emit a signal or do any 421 # pre-processing. 422 if self.batchComputedPropChanges.blocked(): 423 return 424 425 self.signalComputedPropSelectionChanged.emit() 426 427 def _ensureValidPrimPath(self, path): 428 """Validate an input path. If it is a string path, convert it to an 429 Sdf.Path object. 430 """ 431 432 sdfPath = Sdf.Path(str(path)) 433 if not sdfPath.IsAbsoluteRootOrPrimPath(): 434 raise ValueError("Path must be a prim path, got: {}".format( 435 repr(sdfPath))) 436 return sdfPath 437 438 def _validateInstanceIndexParameter(self, instance): 439 """Validate an instance used as a parameter. This can be any positive 440 int or ALL_INSTANCES.""" 441 442 validIndex = False 443 if isinstance(instance, int): 444 if instance >= 0 or instance == ALL_INSTANCES: 445 validIndex = True 446 447 if not validIndex: 448 raise ValueError( 449 "Instance must be a positive int or ALL_INSTANCES" 450 ", got: {}".format(repr(instance))) 451 452 def _ensureValidPropPath(self, prop): 453 """Validate a property.""" 454 455 sdfPath = Sdf.Path(str(prop)) 456 if not sdfPath.IsPropertyPath(): 457 raise ValueError("Path must be a property path, got: {}".format( 458 repr(sdfPath))) 459 return sdfPath 460 461 def _ensureValidTargetPath(self, target): 462 """Validate a property target or connection.""" 463 464 return Sdf.Path(str(target)) 465 466 def _getPropFromPath(self, path): 467 """Get a Usd property object from a property path.""" 468 469 prim = self._rootDataModel.stage.GetPrimAtPath(path.GetPrimPath()) 470 return prim.GetProperty(path.name) 471 472 def _getTargetFromPath(self, path): 473 """Get the Usd object from a target path. It can be either a Usd prim or 474 Usd property. 475 """ 476 477 if path.IsPropertyPath(): 478 return self._getPropFromPath(path) 479 else: 480 return self._rootDataModel.stage.GetPrimAtPath(path) 481 482 def _requireNotBatchingPrims(self): 483 """Raise an error if we are currently batching prim selection changes. 484 We don't want to allow reading prim selection state in the middle of a 485 batch. 486 """ 487 488 if self.batchPrimChanges.blocked(): 489 raise RuntimeError( 490 "Cannot get prim selection state while batching changes.") 491 492 def _requireNotBatchingProps(self): 493 """Raise an error if we are currently batching prop selection changes. 494 We don't want to allow reading prop selection state in the middle of a 495 batch. 496 """ 497 498 if self.batchPropChanges.blocked(): 499 raise RuntimeError( 500 "Cannot get property selection state while batching changes.") 501 502 def _getComputedPropFromPath(self, primPath, propName): 503 """Get a CustomAttribute object from a prim path and property name. 504 Raise an error if the property name does not match any known 505 CustomAttribute. 506 """ 507 508 prim = self._rootDataModel.stage.GetPrimAtPath(primPath) 509 return self._computedPropFactory.getComputedProperty(prim, propName) 510 511 def _requireNotBatchingComputedProps(self): 512 """Raise an error if we are currently batching prop selection changes. 513 We don't want to allow reading prop selection state in the middle of a 514 batch. 515 """ 516 517 if self.batchComputedPropChanges.blocked(): 518 raise RuntimeError("Cannot get computed property selection state " 519 "while batching changes.") 520 521 def _buildPropPath(self, primPath, propName): 522 """Build a new property path from a prim path and a property name.""" 523 524 return Sdf.Path(str(primPath) + "." + propName) 525 526 def _validateComputedPropName(self, propName): 527 """Validate a computed property name.""" 528 529 if propName not in ComputedPropertyNames: 530 raise ValueError("Invalid computed property name: {}".format( 531 repr(propName))) 532 533 def _switchProps(self, fromPrimPath, toPrimPath): 534 """Switch all selected properties from one prim to another. Only do this 535 if all properties currently belong to the "from" prim. 536 """ 537 538 propTargets = self.getPropTargetPaths() 539 computedProps = self.getComputedPropPaths() 540 541 # Check that all properties belong to the "from" prim. 542 for propPath in propTargets: 543 if propPath.GetPrimPath() != fromPrimPath: 544 return 545 for propPrimPath, propName in computedProps: 546 if propPrimPath != fromPrimPath: 547 return 548 549 # Switch all properties to the "to" prim. 550 with self.batchPropChanges: 551 552 self.clearProps() 553 554 # Root prim cannot have non-computed properties. The property paths 555 # in this case are invalid. 556 if str(toPrimPath) != "/": 557 for propPath, targets in propTargets.items(): 558 newPropPath = self._buildPropPath(toPrimPath, propPath.name) 559 self.addPropPath(newPropPath) 560 for target in targets: 561 self.addPropTargetPath(newPropPath, target) 562 563 with self.batchComputedPropChanges: 564 565 self.clearComputedProps() 566 567 for primPath, propName in computedProps: 568 self.addComputedPropPath(toPrimPath, propName) 569 570 ### General Operations ### 571 572 def clear(self): 573 """Clear all selections.""" 574 self.clearPoint() 575 self.clearPrims() 576 self.clearProps() 577 578 def clearPoint(self): 579 self.setPoint(Gf.Vec3f(0.0, 0.0, 0.0)) 580 581 def setPoint(self, point): 582 self._pointSelection = point 583 584 def getPoint(self): 585 return self._pointSelection 586 587 ### Prim Path Operations ### 588 589 def clearPrims(self): 590 """Clear the prim selection (same as path selection).""" 591 592 self._primSelection.clear() 593 self._primSelectionChanged() 594 595 def addPrimPath(self, path, instance=ALL_INSTANCES): 596 """Add a path to the path selection. If an instance is given, only add 597 that instance. 598 """ 599 path = self._ensureValidPrimPath(path) 600 self._validateInstanceIndexParameter(instance) 601 602 self._primSelection.addPrimPath(path, instance) 603 self._primSelectionChanged() 604 605 def removePrimPath(self, path, instance=ALL_INSTANCES): 606 """Remove a path from the path selection. If an instance is given, only 607 remove that instance. If the target does not exist in the selection, do 608 nothing. 609 """ 610 611 path = self._ensureValidPrimPath(path) 612 self._validateInstanceIndexParameter(instance) 613 614 self._primSelection.removePrimPath(path, instance) 615 self._primSelectionChanged() 616 617 def togglePrimPath(self, path, instance=ALL_INSTANCES): 618 """Toggle a path in the path selection. If an instance is given, only 619 that instance is toggled. 620 """ 621 622 path = self._ensureValidPrimPath(path) 623 self._validateInstanceIndexParameter(instance) 624 625 self._primSelection.togglePrimPath(path, instance) 626 self._primSelectionChanged() 627 628 def setPrimPath(self, path, instance=ALL_INSTANCES): 629 """Clear the prim selection then add a single prim path back to the 630 selection. If an instance is given, only add that instance. 631 """ 632 with self.batchPrimChanges: 633 self.clearPrims() 634 self.addPrimPath(path, instance) 635 636 def getFocusPrimPath(self): 637 """Get the path currently in focus.""" 638 639 self._requireNotBatchingPrims() 640 return self._primSelection.getPrimPaths()[0] 641 642 def getPrimPaths(self): 643 """Get a list of all selected paths.""" 644 645 self._requireNotBatchingPrims() 646 return self._primSelection.getPrimPaths() 647 648 def getLCDPaths(self): 649 """Get a list of paths from the selection who do not have an ancestor 650 that is also in the selection. The "Least Common Denominator" paths. 651 """ 652 653 self._requireNotBatchingPrims() 654 return list(self._lcdPathSelection) 655 656 def getPrimPathInstances(self): 657 """Get a dictionary which maps each selected prim to a set of its 658 selected instances. If all of a path's instances are selected, the value 659 is ALL_INSTANCES rather than a set. 660 """ 661 662 self._requireNotBatchingPrims() 663 return self._primSelection.getPrimPathInstances() 664 665 def switchToPrimPath(self, path, instance=ALL_INSTANCES): 666 """Select only the given prim path. If only a single prim was selected 667 before and all selected properties belong to this prim, select the 668 corresponding properties on the new prim instead. If an instance is 669 given, only select that instance. 670 """ 671 672 path = self._ensureValidPrimPath(path) 673 674 oldPrimPaths = self.getPrimPaths() 675 676 with self.batchPrimChanges: 677 self.clearPrims() 678 self.addPrimPath(path, instance) 679 680 if len(oldPrimPaths) == 1: 681 self._switchProps(oldPrimPaths[0], path) 682 683 ### Prim Operations ### 684 # These are all convenience methods which just call their respective path 685 # operations. 686 687 def addPrim(self, prim, instance=ALL_INSTANCES): 688 """Add a prim's path to the path selection. If an instance is given, 689 only add that instance. 690 """ 691 692 self.addPrimPath(prim.GetPath(), instance) 693 694 def removePrim(self, prim, instance=ALL_INSTANCES): 695 """Remove a prim from the prim selection. If an instance is given, only 696 remove that instance. If the target does not exist in the selection, do 697 nothing. 698 """ 699 700 self.removePrimPath(prim.GetPath(), instance) 701 702 def togglePrim(self, prim, instance=ALL_INSTANCES): 703 """Toggle a prim's path in the path selection. If an instance is given, 704 only that instance is toggled. 705 """ 706 707 self.togglePrimPath(prim.GetPath(), instance) 708 709 def setPrim(self, prim, instance=ALL_INSTANCES): 710 """Clear the prim selection then add a single prim back to the 711 selection. If an instance is given, only add that instance. 712 """ 713 714 self.setPrimPath(prim.GetPath(), instance) 715 716 def getFocusPrim(self): 717 """Get the prim whose path is currently in focus.""" 718 719 return self._rootDataModel.stage.GetPrimAtPath(self.getFocusPrimPath()) 720 721 def getPrims(self): 722 """Get a list of all prims whose paths are selected.""" 723 724 return [self._rootDataModel.stage.GetPrimAtPath(path) 725 for path in self.getPrimPaths()] 726 727 def getLCDPrims(self): 728 """Get a list of prims whose paths are both selected and do not have an 729 ancestor that is also in the selection. The "Least Common Denominator" 730 prims. 731 """ 732 733 return [self._rootDataModel.stage.GetPrimAtPath(path) 734 for path in self.getLCDPaths()] 735 736 def getPrimInstances(self): 737 """Get a dictionary which maps each prim whose path is selected to a set 738 of its selected instances. If all of a path's instances are selected, 739 the value is ALL_INSTANCES rather than a set. 740 """ 741 742 return OrderedDict( 743 (self._rootDataModel.stage.GetPrimAtPath(path), instance) 744 for path, instance in self.getPrimPathInstances().items()) 745 746 def switchToPrim(self, prim, instance=ALL_INSTANCES): 747 """Select only the given prim. If only a single prim was selected before 748 and all selected properties belong to this prim, select the 749 corresponding properties on the new prim instead. 750 """ 751 752 self.switchToPrimPath(prim.GetPath(), instance) 753 754 ### Prim Group Removal Operations ### 755 # Convenience methods for removing groups of prims 756 757 def removeInactivePrims(self): 758 """Remove all inactive prims""" 759 for prim in self.getPrims(): 760 if not prim.IsActive(): 761 self.removePrim(prim) 762 763 def removePrototypePrims(self): 764 """Remove all prototype prims""" 765 for prim in self.getPrims(): 766 if prim.IsPrototype() or prim.IsInPrototype(): 767 self.removePrim(prim) 768 769 def removeAbstractPrims(self): 770 """Remove all abstract prims""" 771 for prim in self.getPrims(): 772 if prim.IsAbstract(): 773 self.removePrim(prim) 774 775 def removeUndefinedPrims(self): 776 """Remove all undefined prims""" 777 for prim in self.getPrims(): 778 if not prim.IsDefined(): 779 self.removePrim(prim) 780 781 def removeUnpopulatedPrims(self): 782 """Remove all prim paths whose corresponding prims do not currently 783 exist on the stage. It is the application's responsibility to 784 call this method while it is processing changes to the stage, 785 *before* querying this object for selections. Because this is a 786 synchronization operation rather than an expression of GUI state 787 change, it does *not* perform any notifications/signals, which could 788 cause reentrant application change processing.""" 789 stage = self._rootDataModel.stage 790 self._primSelection.removeMatchingPaths(lambda path: not stage.GetPrimAtPath(path)) 791 self._primSelectionChanged(emitSelChangedSignal=False) 792 793 ### Property Path Operations ### 794 795 def clearProps(self): 796 """Clear the property selection.""" 797 798 self._propSelection.clear() 799 self._propSelectionChanged() 800 801 def addPropPath(self, path): 802 """Add a property to the selection.""" 803 804 path = self._ensureValidPropPath(path) 805 806 primPath = path.GetPrimPath() 807 propName = path.name 808 809 self._propSelection.addPropPath(primPath, propName) 810 self._propSelectionChanged() 811 812 def removePropPath(self, path): 813 """Remove a property from the selection.""" 814 815 path = self._ensureValidPropPath(path) 816 817 primPath = path.GetPrimPath() 818 propName = path.name 819 820 self._propSelection.removePropPath(primPath, propName) 821 self._propSelectionChanged() 822 823 def setPropPath(self, path): 824 """Clear the property selection, then add a single property path back to 825 the selection. 826 """ 827 828 path = self._ensureValidPropPath(path) 829 830 with self.batchPropChanges: 831 self.clearProps() 832 self.addPropPath(path) 833 834 def addPropTargetPath(self, path, targetPath): 835 """Select a property's target or connection.""" 836 837 path = self._ensureValidPropPath(path) 838 targetPath = self._ensureValidTargetPath(targetPath) 839 840 primPath = path.GetPrimPath() 841 propName = path.name 842 843 self._propSelection.addTarget(primPath, propName, targetPath) 844 self._propSelectionChanged() 845 846 def removePropTargetPath(self, path, targetPath): 847 """Deselect a property's target or connection.""" 848 849 path = self._ensureValidPropPath(path) 850 targetPath = self._ensureValidTargetPath(targetPath) 851 852 primPath = path.GetPrimPath() 853 propName = path.name 854 855 self._propSelection.removeTarget(primPath, propName, targetPath) 856 self._propSelectionChanged() 857 858 def setPropTargetPath(self, path, targetPath): 859 """Clear the property selection, then add a single property path back to 860 the selection with a target. 861 """ 862 863 with self.batchPropChanges: 864 self.clearProps() 865 self.addPropTargetPath(path, targetPath) 866 867 def getFocusPropPath(self): 868 """Get the focus property from the property selection.""" 869 870 self._requireNotBatchingProps() 871 872 propPaths = [self._buildPropPath(*propTuple) 873 for propTuple in self._propSelection.getPropPaths()] 874 if len(propPaths) > 0: 875 return propPaths[-1] 876 else: 877 return None 878 879 def getPropPaths(self): 880 """Get a list of all selected properties.""" 881 882 self._requireNotBatchingProps() 883 884 propPaths = [self._buildPropPath(*propTuple) 885 for propTuple in self._propSelection.getPropPaths()] 886 return propPaths 887 888 def getPropTargetPaths(self): 889 """Get a dictionary which maps selected properties to a set of their 890 selected targets or connections. 891 """ 892 893 self._requireNotBatchingProps() 894 895 return OrderedDict((self._buildPropPath(*propTuple), set(targets)) 896 for propTuple, targets in self._propSelection.getTargets().items()) 897 898 ### Property Operations ### 899 900 def addProp(self, prop): 901 """Add a property to the selection.""" 902 903 self.addPropPath(prop.GetPath()) 904 905 def removeProp(self, prop): 906 """Remove a property from the selection.""" 907 908 self.removePropPath(prop.GetPath()) 909 910 def setProp(self, prop): 911 """Clear the property selection, then add a single property back to the 912 selection. 913 """ 914 915 self.setPropPath(prop.GetPath()) 916 917 def addPropTarget(self, prop, target): 918 """Select a property's target or connection.""" 919 920 self.addPropTargetPath(prop.GetPath(), target.GetPath()) 921 922 def removePropTarget(self, prop, target): 923 """Deselect a property's target or connection.""" 924 925 self.removePropTargetPath(prop.GetPath(), target.GetPath()) 926 927 def setPropTarget(self, prop, target): 928 """Clear the property selection, then add a single property back to the 929 selection with a target. 930 """ 931 932 self.removePropTargetPath(prop.GetPath(), target.GetPath()) 933 934 def getFocusProp(self): 935 """Get the focus property from the property selection.""" 936 937 focusPath = self.getFocusPropPath() 938 if focusPath is None: 939 return None 940 941 return self._getPropFromPath(focusPath) 942 943 def getProps(self): 944 """Get a list of all selected properties.""" 945 946 return [self._getPropFromPath(path) 947 for path in self.getPropPaths()] 948 949 def getPropTargets(self): 950 """Get a dictionary which maps selected properties to a set of their 951 selected targets or connections. 952 """ 953 954 propTargets = OrderedDict() 955 for propPath, targetPaths in self.getPropTargetPaths().items(): 956 957 prop = self._getPropFromPath(propPath) 958 targets = {self._getTargetFromPath(target) 959 for target in targetPaths} 960 961 propTargets[prop] = targets 962 963 return propTargets 964 965 ### Computed Property Path Operations ### 966 967 def clearComputedProps(self): 968 """Clear the computed property selection.""" 969 970 self._computedPropSelection.clear() 971 self._computedPropSelectionChanged() 972 973 def addComputedPropPath(self, primPath, propName): 974 """Add a computed property to the selection.""" 975 976 primPath = self._ensureValidPrimPath(primPath) 977 self._validateComputedPropName(propName) 978 979 self._computedPropSelection.addPropPath(primPath, propName) 980 self._computedPropSelectionChanged() 981 982 def removeComputedPropPath(self, primPath, propName): 983 """Remove a computed property from the selection.""" 984 985 primPath = self._ensureValidPrimPath(primPath) 986 self._validateComputedPropName(propName) 987 988 self._computedPropSelection.removePropPath(primPath, propName) 989 self._computedPropSelectionChanged() 990 991 def setComputedPropPath(self, primPath, propName): 992 """Clear the computed property selection, then add a single computed 993 property path back to the selection. 994 """ 995 996 primPath = self._ensureValidPrimPath(primPath) 997 self._validateComputedPropName(propName) 998 999 with self.batchComputedPropChanges: 1000 self.clearComputedProps() 1001 self.addComputedPropPath(primPath, propName) 1002 1003 def getFocusComputedPropPath(self): 1004 """Get the focus computed property from the property selection.""" 1005 1006 self._requireNotBatchingComputedProps() 1007 1008 propPaths = self._computedPropSelection.getPropPaths() 1009 if len(propPaths) > 0: 1010 return propPaths[-1] 1011 else: 1012 return (None, None) 1013 1014 def getComputedPropPaths(self): 1015 """Get a list of all selected computed properties.""" 1016 1017 self._requireNotBatchingComputedProps() 1018 1019 return self._computedPropSelection.getPropPaths() 1020 1021 ### Computed Property Operations ### 1022 1023 def addComputedProp(self, prop): 1024 """Add a computed property to the selection.""" 1025 1026 self.addComputedPropPath(prop.GetPrimPath(), prop.GetName()) 1027 1028 def removeComputedProp(self, prop): 1029 """Remove a computed property from the selection.""" 1030 1031 self.removeComputedPropPath(prop.GetPrimPath(), prop.GetName()) 1032 1033 def setComputedProp(self, prop): 1034 """Clear the computed property selection, then add a single computed 1035 property back to the selection. 1036 """ 1037 1038 self.setComputedPropPath(prop.GetPrimPath(), prop.GetName()) 1039 1040 def getFocusComputedProp(self): 1041 """Get the focus computed property from the property selection.""" 1042 1043 focusPath = self.getFocusComputedPropPath() 1044 if focusPath == (None, None): 1045 return None 1046 1047 return self._getComputedPropFromPath(*focusPath) 1048 1049 def getComputedProps(self): 1050 """Get a list of all selected computed properties.""" 1051 1052 return [self._getComputedPropFromPath(*path) 1053 for path in self.getComputedPropPaths()] 1054