1
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")
23
32
34 """List intermediate references of in the scene, see `FileReference.ls` for
35 more information"""
36 return FileReference.ls(*args, **kwargs)
37
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 = '/'
49 __slots__ = '_refnode'
50
51 @classmethod
53 """:return: (path, copynumber), copynumber is at least 0 """
54 lbraceindex = path.rfind('{')
55 if lbraceindex == -1:
56 return (path, 0)
57
58
59 return (path[:lbraceindex], int(path[lbraceindex+1:-1]))
60
61
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
70
72 """Special treatment for other filereferences"""
73
74 if isinstance(other, FileReference):
75 return self._refnode == other._refnode
76
77 return self.path() == other
78
80 return not self.__eq__(other)
81
84
86 return str(self.path())
87
90
91
92
93
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:
114 nsbasename = filepath.stripext().basename()
115 ns = Namespace.findUnique(nsbasename, incrementFunc = nsfunc)
116 else:
117 ns = Namespace(ns)
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
124 prevns = Namespace.current()
125
126
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
135
136 return FileReference(createdRefpath)
137
138 @undo.notundoable
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
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
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
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
191
192 return importRecursive(self, 0, depth)
193
194
195
196
197
198 @classmethod
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
228
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
236
237 def countTuple(filepath, lut):
238 count = lut.get(filepath, 0)
239 lut[filepath] = count + 1
240 return (filepath , count)
241
242
243 clut = dict()
244 for ref in refs:
245 lut[countTuple(conv(ref.path()), clut)] = ref
246
247
248 clut.clear()
249 for i,path in enumerate(pathscp):
250 pathscp[i] = countTuple(conv(path), clut)
251
252
253 outlist = list()
254 for path in pathscp:
255 ref_or_none = lut.get(path, None)
256 outlist.append(ref_or_none)
257
258
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
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
280 return out
281
282 @classmethod
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
296
297
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
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
349 iter_type = None
350 pred = None
351 if dag:
352
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
365
366
367 if assemblies:
368 nc = MDagPath(n)
369 mdppop(nc, 1)
370 if mdplen(nc) != 0:
371 return False
372
373 elif assembliesInReference:
374 nc = MDagPath(n)
375 mdppop(nc, 1)
376 if mdplen(nc) != 0:
377
378 mfndagSetObject(n)
379 if _isRootOf(rnsrela, mfndagParentNamespace()):
380 return False
381
382
383
384 return True
385
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
399 return True
400
401
402 pred = check_ns
403 iter_type = nt.it.iterDgNodes
404
405
406 kwargs['predicate'] = pred
407
408
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
416
417
418
419 @undo.notundoable
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
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
451 wasloaded = self.isLoaded()
452 self.setLoaded(False)
453
454
455 cmds.setAttr(self._refnode+".locked", state)
456
457
458 self.setLoaded(wasloaded)
459
460 return self
461
462 @undo.notundoable
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():
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
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
491 cmds.file(self.path(copynumber=1), e=1, ns=shortname)
492
493 return self
494
495
496
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
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
519 """:return: True if reference is locked"""
520 return cmds.getAttr(self._refnode + ".locked")
521
523 """:return: True if the reference is loaded"""
524 return cmds.file(rfn=self._refnode, q=1, dr=1) == False
525
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
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]
537 if parentspace:
538 parentspace += ":"
539
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
553 return make_path(path_str)
554
556 """:return: wrapped reference node managing this reference"""
557 import mrv.maya.nt as nt
558 return nt.NodeFromStr(self._refnode)
559
560
561