1
2 """
3 Allows convenient access and handling of namespaces in an object oriented manner
4 """
5 __docformat__ = "restructuredtext"
6
7 import undo
8 from mrv.maya.util import noneToList
9 from mrv.interface import iDagItem
10 from mrv.util import CallOnDeletion
11 import maya.cmds as cmds
12 import maya.OpenMaya as api
13 import re
14
15 __all__ = ("Namespace", "createNamespace", "currentNamespace", "findUniqueNamespace",
16 "existsNamespace", "RootNamespace")
20 """:return: True if other which may be a string, is rooted at 'root
21 :param other: '' = root namespace'
22 hello:world => :hello:world
23 :param root: may be namespace or string. It must not have a ':' in front,
24 hence it must be a relative naespace, and must end with a ':'.
25 :note: the arguments are very specific, but this allows the method
26 to be faster than usual"""
27 return (other+':').startswith(root)
28
31 """ Represents a Maya namespace
32 Namespaces follow the given nameing conventions:
33
34 - Paths starting with a column are absolute
35
36 - :absolute:path
37
38 - Path separator is ':'
39 """
40 re_find_duplicate_sep = re.compile( ":{2,}" )
41 _sep = ':'
42 rootpath = ':'
43 _defaultns = [ 'UI','shared' ]
44 defaultIncrFunc = lambda b,i: "%s%02i" % ( b,i )
45
46
47 __slots__ = tuple()
48
49
50
52 """Initialize the namespace with the given namespace path
53
54 :param namespacepath: the namespace to wrap - it should be absolut to assure
55 relative namespaces will not be interpreted in an unforseen manner ( as they
56 are relative to the currently set namespace
57
58 Set it ":" ( or "" ) to describe the root namespace
59 :param absolute: if True, incoming namespace names will be made absolute if not yet the case
60 :note: the namespace does not need to exist, but many methods will not work if so.
61 NamespaceObjects returned by methods of this class are garantueed to exist"""
62
63 if namespacepath != cls.rootpath:
64 if absolute:
65 if not namespacepath.startswith( ":" ):
66 namespacepath = ":" + namespacepath
67
68 if len( namespacepath ) > 1 and namespacepath.endswith( ":" ):
69 namespacepath = namespacepath[:-1]
70
71 return unicode.__new__( cls, namespacepath )
72
74 """Properly catenate namespace objects - other must be relative namespace or
75 object name ( with or without namespace )
76
77 :return: new string object """
78 inbetween = self._sep
79 if self.endswith( self._sep ) or other.startswith( self._sep ):
80 inbetween = ''
81
82 return "%s%s%s" % ( self, inbetween, other )
83
85 return "Namespace('%s')" % str( self )
86
87
88
89 @classmethod
90 @undo.undoable
91 - def create( cls, namespaceName ):
127
129 """Rename this namespace to newName - the original namespace will cease to exist
130
131 :note: if the namespace already exists, the existing one will be returned with
132 all objects from this one added accordingly
133 :param newName: the absolute name of the new namespace
134 :return: Namespace with the new name
135 :todo: Implement undo !"""
136 newnamespace = self.__class__( newName )
137
138
139
140 def moveChildren( curparent, newname ):
141 for child in curparent.children( ):
142 moveChildren( child, newname + child.basename( ) )
143
144 curparent.delete( move_to_namespace = newname, autocreate=True )
145
146 moveChildren( self, newnamespace )
147 return newnamespace
148
149 - def moveNodes( self, targetNamespace, force = True, autocreate=True ):
150 """Move objects from this to the targetNamespace
151
152 :param force: if True, namespace clashes will be resolved by renaming, if false
153 possible clashes would result in an error
154 :param autocreate: if True, targetNamespace will be created if it does not exist yet
155 :todo: Implement undo !"""
156 targetNamespace = self.__class__( targetNamespace )
157 if autocreate and not targetNamespace.exists( ):
158 targetNamespace = Namespace.create( targetNamespace )
159
160 cmds.namespace( mv=( self, targetNamespace ), force = force )
161
163 """Delete this namespace and move it's obejcts to the given move_to_namespace
164
165 :param move_to_namespace: if None, the namespace to be deleted must be empty
166 If Namespace, objects in this namespace will be moved there prior to namespace deletion
167 move_to_namespace must exist
168 :param autocreate: if True, move_to_namespace will be created if it does not exist yet
169 :note: can handle sub-namespaces properly
170 :raise RuntimeError:
171 :todo: Implement undo !"""
172 if self == self.rootpath:
173 raise ValueError( "Cannot delete root namespace" )
174
175 if not self.exists():
176 return
177
178
179 if move_to_namespace:
180 move_to_namespace = self.__class__( move_to_namespace )
181
182
183 previousns = Namespace.current( )
184 cleanup = CallOnDeletion( None )
185 if previousns != self:
186 cleanup.callableobj = lambda : previousns.setCurrent()
187
188
189
190 for childns in self.children( ):
191 childns.delete( move_to_namespace = move_to_namespace )
192
193
194 self.setCurrent( )
195
196 if move_to_namespace:
197 self.moveNodes( move_to_namespace, autocreate=autocreate )
198
199
200 cmds.namespace( rm=self )
201
202
203 @undo.undoable
216
217
218
228
229 - def children( self, predicate = lambda x: True ):
230 """:return: list of child namespaces
231 :param predicate: return True to include x in result"""
232 lastcurrent = self.current()
233 self.setCurrent( )
234 out = []
235 for ns in noneToList( cmds.namespaceInfo( lon=1 ) ):
236 if ns in self._defaultns or not predicate( ns ):
237 continue
238 out.append( self.__class__( ns ) )
239
240 lastcurrent.setCurrent( )
241
242 return out
243
244
245
246 @classmethod
248 """:return: the currently set absolute namespace """
249
250 nsname = cmds.namespaceInfo( cur = 1 )
251 if not nsname.startswith( ':' ):
252 nsname = ":" + nsname
253 return cls( nsname )
254
255 @classmethod
257 """Find a unique namespace based on basename which does not yet exist
258 in the scene and can be created.
259
260 :param basename: basename of the namespace, like ":mynamespace" or "mynamespace:subspace"
261 :param incrementFunc: func( basename, index ), returns a unique name generated
262 from the basename and the index representing the current iteration
263 :return: unique namespace that is guaranteed not to exist below the current
264 namespace"""
265 i = 0
266 while True:
267 testns = cls( incrementFunc( basename, i ) )
268 i += 1
269
270 if not testns.exists():
271 return testns
272
273 raise AssertionError("Should never get here")
274
276 """:return: True if this namespace exists"""
277 return cmds.namespace( ex=self )
278
280 """
281 :return: True if this namespace is an absolut one, defining a namespace
282 from the root namespace like ":foo:bar"""
283 return self.startswith( self._sep )
284
286 """:return: a relative version of self, thus it does not start with a colon
287 :note: the root namespace cannot be relative - if this is of interest for you,
288 you have to check for it. This method gracefully ignores that fact to make
289 it more convenient to use as one does not have to be afraid of exceptions"""
290 if not self.startswith( ":" ):
291 return self.__class__( self )
292
293 return self.__class__( self[1:], absolute=False )
294
296 """returns this namespace relative to the given basenamespace
297
298 :param basenamespace: the namespace to which the returned one should be
299 relative too
300 :raise ValueError: If this or basenamespace is not absolute or if no relative
301 namespace exists
302 :return: relative namespace"""
303 if not self.isAbsolute() or not basenamespace.isAbsolute( ):
304 raise ValueError( "All involved namespaces need to be absolute: " + self + " , " + basenamespace )
305
306 suffix = self._sep
307 if basenamespace.endswith( self._sep ):
308 suffix = ''
309 relnamespace = self.replace( str( basenamespace ) + suffix, '' )
310 if relnamespace == self:
311 raise ValueError( str( basenamespace ) + " is no base of " + str( self ) )
312
313 return self.__class__( relnamespace, absolute = False )
314
315 @classmethod
317 """Cut the namespace from the given name and return a tuple( namespacename, objectname )
318
319 :note: method assumes that the namespace starts at the beginning of the object"""
320 if objectname.find( '|' ) > -1:
321 raise AssertionError( "Dagpath given where object name is required" )
322
323 rpos = objectname.rfind( Namespace._sep )
324 if rpos == -1:
325 return ( Namespace.rootpath, objectname )
326
327 return ( cls( objectname[:rpos] ), objectname[rpos+1:] )
328
329
333
335 """
336 :return: string with our namespace properly substituted with replacement such
337 that the result is a properly formatted object name ( with or without namespace
338 depending of the value of replacement )
339 As this method is based on string replacement, self might as well match sub-namespaces
340 if it is relative
341 :note: if replacement is an empty string, it will effectively cut the matched namespace
342 off the object name
343 :note: handles replacement of subnamespaces correctly as well
344 :note: as it operates on strings, the actual namespaces do not need to exist"""
345
346 if self == Namespace.rootpath:
347 return self._removeDuplicateSep( self.__class__( replacement, absolute=False ) + find_in )
348
349
350 return self._removeDuplicateSep( find_in.replace( self, replacement ) )
351
352 @classmethod
354 """Same as `substitute`, but signature might feel more natural"""
355 return thisns.substitute( find_in, replacement )
356
357
358
359
360
361
363 """Return an iterator on all objects in the namespace
364
365 :param args: MFn.kType filter types to be used to pre-filter the nodes
366 in the namespace. This can greatly improve performance !
367 :param kwargs: given to `iterDagNodes` or `iterDgNodes`, which includes the
368 option to provide a predicate function. Additionally, the following ones
369 may be defined:
370
371 * asNode:
372 if true, default True, Nodes will be yielded. If False,
373 you will receive MDagPaths or MObjects depending on the 'dag' kwarg
374
375 * dag:
376 if True, default False, only dag nodes will be returned, otherwise you will
377 receive dag nodes and dg nodes. Instance information will be lost on the way
378 though.
379
380 * depth:
381 if 0, default 0, only objects in this namespace will be returned
382
383 if -1, all subnamespaces will be included as well, the depth is unlimited
384
385 if 0<depth<x include all objects up to the 'depth' subnamespace
386 :note: this method is quite similar to `FileReference.iterNodes`, but
387 has a different feature set and needs this code here for maximum performance"""
388 import nt
389 dag = kwargs.pop('dag', False)
390 asNode = kwargs.get('asNode', True)
391 predicate = kwargs.pop('predicate', lambda n: True)
392 depth = kwargs.pop('depth', 0)
393
394
395 kwargs['asNode'] = False
396 pred = None
397 iter_type = None
398 nsGetRelativeTo = type(self).relativeTo
399 selfrela = self.toRelative()+':'
400 if dag:
401 mfndag = api.MFnDagNode()
402 mfndagSetObject = mfndag.setObject
403 mfndagParentNamespace = mfndag.parentNamespace
404
405 def check_filter(n):
406 mfndagSetObject(n)
407 ns = mfndagParentNamespace()
408 if not _isRootOf(selfrela, ns):
409 return False
410
411
412 if depth > -1:
413 ns = Namespace(ns)
414 if self == ns:
415 return True
416
417
418 if nsGetRelativeTo(ns, self).count(':')+1 > depth:
419 return False
420
421 return True
422
423
424 iter_type = nt.it.iterDagNodes
425 pred = check_filter
426 else:
427 iter_type = nt.it.iterDgNodes
428 mfndep = api.MFnDependencyNode()
429 mfndepSetObject = mfndep.setObject
430 mfndepParentNamespace = mfndep.parentNamespace
431
432 def check_filter(n):
433 mfndepSetObject(n)
434 ns = mfndepParentNamespace()
435 if not _isRootOf(selfrela, ns):
436 return False
437
438
439
440
441 if depth > -1:
442 ns = Namespace(ns)
443 if self == ns:
444 return True
445
446 if nsGetRelativeTo(ns, self).count(':')+1 > depth:
447 return False
448
449 return True
450
451 iter_type = nt.it.iterDgNodes
452 pred = check_filter
453
454
455
456 kwargs['predicate'] = pred
457 NodeFromObj = nt.NodeFromObj
458 for n in iter_type(*args, **kwargs):
459 if asNode:
460 n = NodeFromObj(n)
461 if predicate(n):
462 yield n
463
471
475
479
483
484
485 RootNamespace = Namespace(Namespace.rootpath)
486
487
488