Package mrv :: Package maya :: Module ref
[hide private]
[frames] | no frames]

Source Code for Module mrv.maya.ref

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3  Allows convenient access and handling of references in an object oriented manner 
  4  """ 
  5  __docformat__ = "restructuredtext" 
  6   
  7  from mrv.path import make_path 
  8  from mrv.util import And 
  9  from mrv.exc import MRVError 
 10  from mrv.maya.ns import Namespace, _isRootOf 
 11  from mrv.maya.util import noneToList 
 12  from mrv.interface import iDagItem 
 13  import undo 
 14  import maya.cmds as cmds 
 15  import maya.OpenMaya as api 
 16  from itertools import ifilter 
 17   
 18  __all__ = ("createReference", "listReferences", "FileReference", "FileReferenceError") 
19 20 #{ Exceptions 21 -class FileReferenceError(MRVError):
22 pass
23
24 #} 25 26 27 #{ Utilities 28 29 -def createReference(*args, **kwargs):
30 """create a new reference, see `FileReference.create` for more information""" 31 return FileReference.create(*args, **kwargs)
32
33 -def listReferences(*args, **kwargs):
34 """List intermediate references of in the scene, see `FileReference.ls` for 35 more information""" 36 return FileReference.ls(*args, **kwargs)
37
38 #} END utilities 39 40 41 -class FileReference(iDagItem):
42 """Represents a Maya file reference 43 44 :note: do not cache these instances but get a fresh one when you have to work with it 45 :note: as FileReference is also a iDagItem, all the respective methods, especially for 46 parent/child iteration and query can be used as well""" 47 editTypes = [ 'setAttr','addAttr','deleteAttr','connectAttr','disconnectAttr','parent'] 48 _sep = '/' # iDagItem configuration 49 __slots__ = '_refnode' 50 51 @classmethod
52 - def _splitCopyNumber(cls, path):
53 """:return: (path, copynumber), copynumber is at least 0 """ 54 lbraceindex = path.rfind('{') 55 if lbraceindex == -1: 56 return (path, 0) 57 # END handle no brace found 58 59 return (path[:lbraceindex], int(path[lbraceindex+1:-1]))
60 61 #{ Object Overrides
62 - def __init__(self, filepath = None, refnode = None):
63 if refnode: 64 self._refnode = str(refnode) 65 elif filepath: 66 self._refnode = cmds.referenceQuery(filepath, rfn=1) 67 else: 68 raise ValueError("Specify either filepath or refnode to initialize the instance from")
69 # END handle input 70
71 - def __eq__(self, other):
72 """Special treatment for other filereferences""" 73 # need equal copy numbers as well as equal paths - the refnode encapsulates all this 74 if isinstance(other, FileReference): 75 return self._refnode == other._refnode 76 77 return self.path() == other
78
79 - def __ne__(self, other):
80 return not self.__eq__(other)
81
82 - def __hash__(self):
83 return hash(self.path(copynumber=1))
84
85 - def __str__(self):
86 return str(self.path())
87
88 - def __repr__(self):
89 return "FileReference(%s)" % str(self.path(copynumber=1))
90 91 #} END object overrides 92 93 #{ Reference Adjustments 94 @classmethod
95 - def create(cls, filepath, namespace=None, load = True, **kwargs):
96 """Create a reference with the given namespace 97 98 :param filepath: path describing the reference file location 99 :param namespace: if None, a unique namespace will be generated for you 100 The namespace will contain all referenced objects. 101 :param load: if True, the reference will be created in loaded state, other 102 wise its loading is deferred 103 :param kwargs: passed to file command 104 :raise ValueError: if the namespace does already exist 105 :raise RuntimeError: if the reference could not be created""" 106 filepath = make_path(cls._splitCopyNumber(filepath)[0]) 107 108 def nsfunc(base, i): 109 if not i: return base 110 return "%s%i" % (base,i)
111 112 ns = namespace 113 if not ns: # assure unique namespace 114 nsbasename = filepath.stripext().basename() 115 ns = Namespace.findUnique(nsbasename, incrementFunc = nsfunc) 116 else: 117 ns = Namespace(ns) # assure we have a namespace object 118 119 ns = ns.relativeTo(Namespace(Namespace.rootpath)) 120 if ns.exists(): 121 raise ValueError("Namespace %s for %s does already exist" % (ns,filepath)) 122 123 # assure we keep the current namespace 124 prevns = Namespace.current() 125 126 # removing duplicate **kwargs 127 kwargs.pop('ns', None) 128 kwargs.pop('reference', kwargs.pop('r', None)) 129 kwargs.pop('deferReference', kwargs.pop('dr', None)) 130 try: 131 createdRefpath = cmds.file(filepath, ns=str(ns),r=1,dr=not load, **kwargs) 132 finally: 133 prevns.setCurrent() 134 # END assure we keep the namespace 135 136 return FileReference(createdRefpath)
137 138 @undo.notundoable
139 - def remove(self, **kwargs):
140 """ Remove the given reference from the scene 141 142 :note: assures that no namespaces of that reference are left, remaining objects 143 will be moved into the root namespace. This way the namespaces will not be left as waste. 144 This fails if there are referenced objects in the subnamespace - we currently 145 ignore that issue as the main reference removal worked at that point. 146 :note: kwargs passed to namespace.delete """ 147 ns = self.namespace() 148 cmds.file(self.path(copynumber=1), rr=1) 149 try: 150 ns.delete(**kwargs) 151 except RuntimeError: 152 pass
153 154 @undo.notundoable
155 - def replace(self, filepath):
156 """Replace this reference with filepath 157 158 :param filepath: the path to the file to replace this reference with 159 Reference instances will be handled as well. 160 :return: self""" 161 filepath = (isinstance(filepath, type(self)) and filepath.path()) or filepath 162 filepath = self._splitCopyNumber(filepath)[0] 163 cmds.file(filepath, lr=self._refnode) 164 return self
165 166 @undo.notundoable
167 - def importRef(self, depth=0):
168 """Import the reference until the given depth is reached 169 170 :param depth: 171 - x<1: import all references and subreferences 172 - x: import until level x is reached, 0 imports just self such that 173 all its children are on the same level as self was before import 174 :return: list of FileReference objects that are now in the root namespace - this 175 list could be empty if all subreferences are fully imported""" 176 def importRecursive(reference, curdepth, maxdepth): 177 # load ref 178 reference.setLoaded(True) 179 children = reference.children() 180 cmds.file(reference.path(copynumber=1), importReference=1) 181 182 if curdepth == maxdepth: 183 return children 184 185 outsubrefs = list() 186 for childref in children: 187 outsubrefs.extend(importRecursive(childref, curdepth+1, maxdepth)) 188 189 return outsubrefs
190 # END importRecursive 191 192 return importRecursive(self, 0, depth) 193 194 # } END reference adjustments 195 196 #{ Listing 197 198 @classmethod
199 - def fromPaths(cls, paths, **kwargs):
200 """Find the reference for each path in paths. If you provide the path X 201 2 times, but you only have one reference to X, the return value will be 202 [FileReference(X), None] as there are less references than provided paths. 203 204 :param paths: a list of paths or references whose references in the scene 205 should be returned. In case a reference is found, its plain path will be 206 used instead. 207 :param kwargs: all supported by `ls` to yield the base set of references 208 we will use to match the paths with. Additionally, you may specify: 209 210 * ignore_extension: 211 if True, default False, the extension will be ignored 212 during the search, only the actual base name will matter. 213 This way, an MA file will be matched with an MB file. 214 The references returned will still have their extension original extension. 215 216 :return: list(FileReference|None, ...) 217 if a filereference was found for given occurrence of Path, it will be returned 218 at index of the current path in the input paths, otherwise it is None. 219 :note: zip(paths, result) to get a corresponding tuple list associating each input path 220 with the located reference""" 221 if not isinstance(paths, (list,tuple)) or hasattr(paths, 'next'): 222 raise TypeError("paths must be tuple, was %s" % type(paths)) 223 224 ignore_ext = kwargs.pop("ignore_extension", False) 225 refs = cls.ls(**kwargs) 226 227 # build dict for fast lookup 228 # It will keep each reference 229 lut = dict() 230 pathscp = [(isinstance(p, cls) and p.path()) or make_path(p) for p in paths] 231 232 conv = lambda f: f 233 if ignore_ext: 234 conv = lambda f: f.expandvars().splitext()[0] 235 # END ignore extension converter 236 237 def countTuple(filepath, lut): 238 count = lut.get(filepath, 0) 239 lut[filepath] = count + 1 240 return (filepath , count)
241 # END utility 242 243 clut = dict() 244 for ref in refs: 245 lut[countTuple(conv(ref.path()), clut)] = ref # keys have no ext 246 # END for each ref to put into lut 247 248 clut.clear() 249 for i,path in enumerate(pathscp): 250 pathscp[i] = countTuple(conv(path), clut) 251 # END for each path to prepare 252 253 outlist = list() 254 for path in pathscp: 255 ref_or_none = lut.get(path, None) 256 outlist.append(ref_or_none) 257 # no need to delete the keys as they have to be unique anyway 258 # END for each path to find 259 return outlist 260 261 @classmethod
262 - def ls(cls, rootReference = "", predicate = lambda x: True):
263 """list all references in the scene or under the given root 264 265 :param rootReference: if not empty, the references below it will be returned. 266 Otherwise all scene references will be listed. 267 May be string, Path or FileReference 268 :param predicate: method returning true for each valid file reference object that 269 should be part of the return value. 270 :return: list of `FileReference` s objects""" 271 if isinstance(rootReference, cls): 272 rootReference = rootReference.path(copynumber=1) 273 # END handle non-string type 274 out = list() 275 for reffile in cmds.file(str(rootReference), q=1, r=1): 276 refinst = FileReference(filepath = reffile) 277 if predicate(refinst): 278 out.append(refinst) 279 # END for each reference file 280 return out
281 282 @classmethod
283 - def lsDeep(cls, predicate = lambda x: True, **kwargs):
284 """Return all references recursively 285 286 :param kwargs: support for arguments as in `ls`, hence you can use the 287 rootReference flag to restrict the set of returned FileReferences.""" 288 kwargs['predicate'] = predicate 289 refs = cls.ls(**kwargs) 290 out = refs 291 for ref in refs: 292 out.extend(ref.childrenDeep(order=cls.kOrder_BreadthFirst, predicate=predicate)) 293 return out
294 295 #} listing 296 297 #{ Nodes Query
298 - def iterNodes(self, *args, **kwargs):
299 """Creates iterator over nodes in this reference 300 301 :param args: MFn.kType filter ids to be used to pre-filter all nodes. 302 If you know what you are looking for, setting this can greatly improve 303 performance ! 304 :param kwargs: additional kwargs will be passed to either `iterDagNodes` 305 or `iterDgNodes` (dag = False). The following additional kwargs may 306 be specified: 307 308 * asNode: 309 if True, default True, return wrapped Nodes, if False MDagPaths 310 or MObjects will be returned 311 312 * dag: 313 if True, default False, return dag nodes only. Otherwise return dependency nodes 314 as well. Enables assemblies and assembilesInReference. 315 316 * assemblies: 317 if True, return only dagNodes with no parent. Needs dag and 318 is mutually exclusive with assembilesInReference. 319 320 * assembliesInReference: 321 if True, return only dag nodes that have no 322 parent in their own reference. They may have a parent not coming from their 323 reference though. This flag has a big negative performance impact and requires dag. 324 325 * predicate: 326 if function returns True for Node|MObject|MDagPath n, n will be yielded. 327 Defaults to return True for all. 328 :raise ValueError: if incompatible arguments have been given""" 329 import nt 330 331 rns = self.namespace() 332 rnsrela = rns.toRelative()+':' 333 asNode = kwargs.get('asNode', True) 334 predicate = kwargs.get('predicate', lambda n: True) 335 kwargs['asNode'] = False # we will do it 336 337 dag = kwargs.pop('dag', False) 338 assemblies = kwargs.pop('assemblies', False) 339 assembliesInReference = kwargs.pop('assembliesInReference', False) 340 341 342 if (assemblies or assembliesInReference) and not dag: 343 raise ValueError("Cannot list assemblies of any kind if dag is not specified") 344 345 if assemblies and assembliesInReference: 346 raise ValueError("assemblies and assembilesInReference are mutually exclusive") 347 348 # CONSTRUCT PREDICATE 349 iter_type = None 350 pred = None 351 if dag: 352 # cache functions for 10% more performance 353 mfndag = api.MFnDagNode() 354 mfndagSetObject = mfndag.setObject 355 mfndagParentNamespace = mfndag.parentNamespace 356 MDagPath = api.MDagPath 357 mdppop = MDagPath.pop 358 mdplen = MDagPath.length 359 360 def check_dag_ns(n): 361 mfndagSetObject(n) 362 if not _isRootOf(rnsrela, mfndagParentNamespace()): 363 return False 364 # END first namespace check 365 366 # assemblies have no parents 367 if assemblies: 368 nc = MDagPath(n) 369 mdppop(nc, 1) 370 if mdplen(nc) != 0: 371 return False 372 # END check length 373 elif assembliesInReference: 374 nc = MDagPath(n) 375 mdppop(nc, 1) 376 if mdplen(nc) != 0: 377 # check whether parent is in a different namespace 378 mfndagSetObject(n) 379 if _isRootOf(rnsrela, mfndagParentNamespace()): 380 return False 381 # END check parent rns 382 # END check length 383 # END handle assemblies 384 return True
385 # END filter 386 387 pred = check_dag_ns 388 iter_type = nt.it.iterDagNodes 389 else: 390 mfndep = api.MFnDependencyNode() 391 mfndepSetObject = mfndep.setObject 392 mfndepParentNamespace = mfndep.parentNamespace 393 394 def check_ns(n): 395 mfndepSetObject(n) 396 if not _isRootOf(rnsrela, mfndepParentNamespace()): 397 return False 398 # END first namespace check 399 return True 400 # END filter 401 402 pred = check_ns 403 iter_type = nt.it.iterDgNodes 404 # END handle dag/dg mode predicate 405 406 kwargs['predicate'] = pred 407 408 # have to iterate it manually in order to get the toNode conversion right 409 NodeFromObj = nt.NodeFromObj 410 for n in iter_type(*args, **kwargs): 411 if asNode: 412 n = NodeFromObj(n) 413 if predicate(n): 414 yield n 415 # END for each node in iteartion 416 #} nodes query 417 418 #{ Edit 419 @undo.notundoable
420 - def cleanup(self, unresolvedEdits = True, editTypes = editTypes):
421 """remove unresolved edits or all edits on this reference 422 423 :param unresolvedEdits: if True, only dangling connections will be removed, 424 if False, all reference edits will be removed - the reference will be unloaded for beforehand. 425 The loading state of the reference will stay unchanged after the operation. 426 :param editTypes: list of edit types to remove during cleanup 427 :return: self""" 428 wasloaded = self.isLoaded() 429 if not unresolvedEdits: 430 self.setLoaded(False) 431 432 for etype in editTypes: 433 cmds.file(cr=self._refnode, editCommand=etype) 434 435 if not unresolvedEdits: 436 self.setLoaded(wasloaded) 437 438 return self
439 440 @undo.notundoable
441 - def setLocked(self, state):
442 """Set the reference to be locked or unlocked 443 444 :param state: if True, the reference is locked , if False its unlocked and 445 can be altered 446 :return: self""" 447 if self.isLocked() == state: 448 return 449 450 # unload ref 451 wasloaded = self.isLoaded() 452 self.setLoaded(False) 453 454 # set locked 455 cmds.setAttr(self._refnode+".locked", state) 456 457 # reset the loading state 458 self.setLoaded(wasloaded) 459 460 return self
461 462 @undo.notundoable
463 - def setLoaded(self, state):
464 """set the reference loaded or unloaded 465 466 :param state: True = unload reference, True = load reference 467 :return: self""" 468 469 if state == self.isLoaded(): # already desired state 470 return 471 472 if state: 473 cmds.file(loadReference=self._refnode) 474 else: 475 cmds.file(unloadReference=self._refnode) 476 477 return self
478 479 @undo.notundoable
480 - def setNamespace(self, namespace):
481 """set the reference to use the given namespace 482 483 :param namespace: Namespace instance or name of the short namespace 484 :raise RuntimeError: if namespace already exists or if reference is not root 485 :return: self""" 486 shortname = namespace 487 if isinstance(namespace, Namespace): 488 shortname = namespace.basename() 489 490 # set the namespace 491 cmds.file(self.path(copynumber=1), e=1, ns=shortname) 492 493 return self
494 495 #}END edit 496
497 - def parent(self):
498 """:return: the parent reference of this instance or None if we are root""" 499 parentrfn = cmds.referenceQuery(self._refnode, rfn=1, p=1) 500 if not parentrfn: 501 return None 502 return FileReference(refnode = parentrfn)
503
504 - def children(self , predicate = lambda x: True):
505 """ :return: all intermediate child references of this instance """ 506 return self.ls(rootReference = self, predicate = predicate)
507 508 #{ Query
509 - def exists(self):
510 """:return: True if our file reference exists in maya""" 511 try: 512 self.path(copynumber=1) 513 except RuntimeError: 514 return False 515 else: 516 return True
517
518 - def isLocked(self):
519 """:return: True if reference is locked""" 520 return cmds.getAttr(self._refnode + ".locked")
521
522 - def isLoaded(self):
523 """:return: True if the reference is loaded""" 524 return cmds.file(rfn=self._refnode, q=1, dr=1) == False
525
526 - def copynumber(self):
527 """:return: the references copy number - starting at 0 for the first reference 528 :note: we do not cache the copy number as mayas internal numbering can change on 529 when references change - the only stable thing is the reference node name""" 530 return self._splitCopyNumber(self.path(copynumber=1))[1]
531
532 - def namespace(self):
533 """:return: namespace object of the full namespace holding all objects in this reference""" 534 fullpath = self.path(copynumber=1) 535 refspace = cmds.file(fullpath, q=1, ns=1) 536 parentspace = cmds.file(fullpath, q=1, pns=1)[0] # returns lists, although its always just one string 537 if parentspace: 538 parentspace += ":" 539 # END handle parent namespace 540 return Namespace(":" + parentspace + refspace)
541
542 - def path(self, copynumber=False, unresolved = False):
543 """:return: Path object with the path containing the reference's data 544 :param copynumber: If True, the returned path will include the copy number. 545 As it will be a path object, it might not be fully usable in that state 546 :param unresolved: see `ls` 547 :note: we always query it from maya as our numbers change if some other 548 reference is being removed and cannot be trusted""" 549 path_str = cmds.referenceQuery(self._refnode, f=1, un=unresolved) 550 if not copynumber: 551 path_str = self._splitCopyNumber(path_str)[0] 552 # END handle copy number 553 return make_path(path_str)
554
555 - def referenceNode(self):
556 """:return: wrapped reference node managing this reference""" 557 import mrv.maya.nt as nt 558 return nt.NodeFromStr(self._refnode)
559 560 #}END query methods 561