3
Allows convenient access and handling of namespaces in an object oriented manner
5
__docformat__ = "restructuredtext"
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
15
__all__ = ("Namespace", "createNamespace", "currentNamespace", "findUniqueNamespace",
16
"existsNamespace", "RootNamespace")
19
def _isRootOf( root, other ):
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
#} END internal utilities
30
class Namespace( unicode, iDagItem ):
31
""" Represents a Maya namespace
32
Namespaces follow the given nameing conventions:
34
- Paths starting with a column are absolute
38
- Path separator is ':'
40
re_find_duplicate_sep = re.compile( ":{2,}" )
43
_defaultns = [ 'UI','shared' ] # default namespaces that we want to ignore in our listings
44
defaultIncrFunc = lambda b,i: "%s%02i" % ( b,i )
46
# to keep instance small
51
def __new__( cls, namespacepath=rootpath, absolute = True ):
52
"""Initialize the namespace with the given namespace path
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
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"""
63
if namespacepath != cls.rootpath:
65
if not namespacepath.startswith( ":" ): # do not force absolute namespace !
66
namespacepath = ":" + namespacepath
68
if len( namespacepath ) > 1 and namespacepath.endswith( ":" ):
69
namespacepath = namespacepath[:-1]
70
# END if its not the root namespace
71
return unicode.__new__( cls, namespacepath )
73
def __add__( self, other ):
74
"""Properly catenate namespace objects - other must be relative namespace or
75
object name ( with or without namespace )
77
:return: new string object """
79
if self.endswith( self._sep ) or other.startswith( self._sep ):
82
return "%s%s%s" % ( self, inbetween, other )
85
return "Namespace('%s')" % str( self )
86
#}END Overridden Methods
91
def create( cls, namespaceName ):
92
"""Create a new namespace
94
:param namespaceName: the name of the namespace, absolute or relative -
95
it may contain subspaces too, i.e. :foo:bar.
96
fred:subfred is a relative namespace, created in the currently active namespace
97
:note: if the target namespace already exists, it will be returned
98
:return: the create Namespace object"""
99
newns = cls( namespaceName )
101
if newns.exists(): # skip work
104
cleanup = CallOnDeletion( None )
105
if newns.isAbsolute(): # assure root is current if we are having an absolute name
106
previousns = Namespace.current()
107
cls( Namespace.rootpath ).setCurrent( )
108
cleanup.callableobj = lambda : previousns.setCurrent()
110
# create each token accordingly ( its not root here )
111
tokens = newns.split( newns._sep )
112
for i,token in enumerate( tokens ): # skip the root namespac
113
base = cls( ':'.join( tokens[:i+1] ) )
117
# otherwise add the baes to its parent ( that should exist
118
# put operation on the queue - as we create empty namespaces, we can delete
120
op = undo.GenericOperation( )
121
op.setDoitCmd( cmds.namespace, p=base.parent() , add=base.basename() )
122
op.setUndoitCmd(cmds.namespace, rm=base )
128
def rename( self, newName ):
129
"""Rename this namespace to newName - the original namespace will cease to exist
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 )
139
# recursively move children
140
def moveChildren( curparent, newname ):
141
for child in curparent.children( ):
142
moveChildren( child, newname + child.basename( ) )
143
# all children should be gone now, move the
144
curparent.delete( move_to_namespace = newname, autocreate=True )
145
# END internal method
146
moveChildren( self, newnamespace )
149
def moveNodes( self, targetNamespace, force = True, autocreate=True ):
150
"""Move objects from this to the targetNamespace
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 )
160
cmds.namespace( mv=( self, targetNamespace ), force = force )
162
def delete( self, move_to_namespace = rootpath, autocreate=True ):
163
"""Delete this namespace and move it's obejcts to the given move_to_namespace
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
171
:todo: Implement undo !"""
172
if self == self.rootpath:
173
raise ValueError( "Cannot delete root namespace" )
175
if not self.exists(): # its already gone - all fine
178
# assure we have a namespace type
179
if move_to_namespace:
180
move_to_namespace = self.__class__( move_to_namespace )
182
# assure we do not loose the current namespace - the maya methods could easily fail
183
previousns = Namespace.current( )
184
cleanup = CallOnDeletion( None )
185
if previousns != self: # cannot reset future deleted namespace
186
cleanup.callableobj = lambda : previousns.setCurrent()
189
# recurse into children for deletion
190
for childns in self.children( ):
191
childns.delete( move_to_namespace = move_to_namespace )
193
# make ourselves current
196
if move_to_namespace:
197
self.moveNodes( move_to_namespace, autocreate=autocreate )
199
# finally delete the namespace
200
cmds.namespace( rm=self )
202
# need to fully qualify it as undo is initialized after us ...
204
def setCurrent( self ):
205
"""Set this namespace to be the current one - new objects will be put in it
210
melop = undo.GenericOperation( )
211
melop.setDoitCmd( cmds.namespace, set = self )
212
melop.setUndoitCmd( cmds.namespace, set = Namespace.current() )
220
""":return: parent namespace of this instance"""
221
if self == self.rootpath:
224
parent = iDagItem.parent( self ) # considers children like ":bar" being a root
225
if parent == None: # we are just child of the root namespcae
226
parent = self.rootpath
227
return self.__class__( parent )
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()
235
for ns in noneToList( cmds.namespaceInfo( lon=1 ) ): # returns root-relative names !
236
if ns in self._defaultns or not predicate( ns ):
238
out.append( self.__class__( ns ) )
239
# END for each subspace
240
lastcurrent.setCurrent( )
248
""":return: the currently set absolute namespace """
249
# will return namespace relative to the root - thus is absolute in some sense
250
nsname = cmds.namespaceInfo( cur = 1 )
251
if not nsname.startswith( ':' ): # assure we return an absoslute namespace
252
nsname = ":" + nsname
256
def findUnique( cls, basename, incrementFunc = defaultIncrFunc ):
257
"""Find a unique namespace based on basename which does not yet exist
258
in the scene and can be created.
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
267
testns = cls( incrementFunc( basename, i ) )
270
if not testns.exists():
273
raise AssertionError("Should never get here")
276
""":return: True if this namespace exists"""
277
return cmds.namespace( ex=self )
279
def isAbsolute( self ):
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 )
285
def toRelative( self ):
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 ) # create a copy
293
return self.__class__( self[1:], absolute=False )
295
def relativeTo( self, basenamespace ):
296
"""returns this namespace relative to the given basenamespace
298
:param basenamespace: the namespace to which the returned one should be
300
:raise ValueError: If this or basenamespace is not absolute or if no relative
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 )
307
if basenamespace.endswith( self._sep ):
309
relnamespace = self.replace( str( basenamespace ) + suffix, '' )
310
if relnamespace == self:
311
raise ValueError( str( basenamespace ) + " is no base of " + str( self ) )
313
return self.__class__( relnamespace, absolute = False )
316
def splitNamespace( cls, objectname ):
317
"""Cut the namespace from the given name and return a tuple( namespacename, objectname )
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" )
323
rpos = objectname.rfind( Namespace._sep )
325
return ( Namespace.rootpath, objectname )
327
return ( cls( objectname[:rpos] ), objectname[rpos+1:] )
330
def _removeDuplicateSep( self, name ):
331
""":return: name with duplicated : removed"""
332
return self.re_find_duplicate_sep.sub( self._sep, name )
334
def substitute( self, find_in, replacement ):
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
341
:note: if replacement is an empty string, it will effectively cut the matched namespace
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
# special case : we are root
346
if self == Namespace.rootpath:
347
return self._removeDuplicateSep( self.__class__( replacement, absolute=False ) + find_in )
350
return self._removeDuplicateSep( find_in.replace( self, replacement ) )
353
def substituteNamespace( cls, thisns, find_in, replacement ):
354
"""Same as `substitute`, but signature might feel more natural"""
355
return thisns.substitute( find_in, replacement )
362
def iterNodes( self, *args, **kwargs ):
363
"""Return an iterator on all objects in the namespace
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
372
if true, default True, Nodes will be yielded. If False,
373
you will receive MDagPaths or MObjects depending on the 'dag' kwarg
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
381
if 0, default 0, only objects in this namespace will be returned
383
if -1, all subnamespaces will be included as well, the depth is unlimited
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"""
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)
394
# we handle node conversion
395
kwargs['asNode'] = False
398
nsGetRelativeTo = type(self).relativeTo
399
selfrela = self.toRelative()+':'
401
mfndag = api.MFnDagNode()
402
mfndagSetObject = mfndag.setObject
403
mfndagParentNamespace = mfndag.parentNamespace
407
ns = mfndagParentNamespace()
408
if not _isRootOf(selfrela, ns):
414
if self == ns: # its depth 0
417
# one separator means two subpaths
418
if nsGetRelativeTo(ns, self).count(':')+1 > depth:
424
iter_type = nt.it.iterDagNodes
427
iter_type = nt.it.iterDgNodes
428
mfndep = api.MFnDependencyNode()
429
mfndepSetObject = mfndep.setObject
430
mfndepParentNamespace = mfndep.parentNamespace
434
ns = mfndepParentNamespace()
435
if not _isRootOf(selfrela, ns):
437
# END first namespace check
439
# duplicated to be as fast as possible
443
if self == ns: # its depth 0
446
if nsGetRelativeTo(ns, self).count(':')+1 > depth:
451
iter_type = nt.it.iterDgNodes
456
kwargs['predicate'] = pred
457
NodeFromObj = nt.NodeFromObj
458
for n in iter_type(*args, **kwargs):
463
# END for each object to yield
464
#} END object retrieval
468
def createNamespace( *args ):
469
"""see `Namespace.create`"""
470
return Namespace.create( *args )
472
def currentNamespace( ):
473
"""see `Namespace.current`"""
474
return Namespace.current()
476
def findUniqueNamespace( *args, **kwargs ):
477
"""see `Namespace.findUnique`"""
478
return Namespace.findUnique( *args, **kwargs )
480
def existsNamespace( namespace ):
481
""":return : True if given namespace ( name ) exists"""
482
return Namespace( namespace ).exists()
485
RootNamespace = Namespace(Namespace.rootpath)