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

Source Code for Module mrv.maya.ns

  1  # -*- coding: utf-8 -*- 
  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") 
17 18 #{ internal utilties 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
29 30 -class Namespace( unicode, iDagItem ):
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' ] # default namespaces that we want to ignore in our listings 44 defaultIncrFunc = lambda b,i: "%s%02i" % ( b,i ) 45 46 # to keep instance small 47 __slots__ = tuple() 48 49 #{ Overridden Methods 50
51 - def __new__( cls, namespacepath=rootpath, absolute = True ):
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( ":" ): # do not force absolute namespace ! 66 namespacepath = ":" + namespacepath 67 # END if absolute 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 )
72
73 - def __add__( self, other ):
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
84 - def __repr__( self ):
85 return "Namespace('%s')" % str( self )
86 #}END Overridden Methods 87 88 #{Edit Methods 89 @classmethod 90 @undo.undoable
91 - def create( cls, namespaceName ):
92 """Create a new namespace 93 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 ) 100 101 if newns.exists(): # skip work 102 return newns 103 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() 109 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] ) ) 114 if base.exists( ): 115 continue 116 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 119 # them at any time 120 op = undo.GenericOperation( ) 121 op.setDoitCmd( cmds.namespace, p=base.parent() , add=base.basename() ) 122 op.setUndoitCmd(cmds.namespace, rm=base ) 123 op.doIt( ) 124 # END for each token 125 126 return newns
127
128 - def rename( self, newName ):
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 # 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 ) 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
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 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(): # its already gone - all fine 176 return 177 178 # assure we have a namespace type 179 if move_to_namespace: 180 move_to_namespace = self.__class__( move_to_namespace ) 181 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() 187 188 189 # recurse into children for deletion 190 for childns in self.children( ): 191 childns.delete( move_to_namespace = move_to_namespace ) 192 193 # make ourselves current 194 self.setCurrent( ) 195 196 if move_to_namespace: 197 self.moveNodes( move_to_namespace, autocreate=autocreate ) 198 199 # finally delete the namespace 200 cmds.namespace( rm=self )
201 202 # need to fully qualify it as undo is initialized after us ... 203 @undo.undoable
204 - def setCurrent( self ):
205 """Set this namespace to be the current one - new objects will be put in it 206 by default 207 208 :return: self""" 209 # THIS IS FASTER ! 210 melop = undo.GenericOperation( ) 211 melop.setDoitCmd( cmds.namespace, set = self ) 212 melop.setUndoitCmd( cmds.namespace, set = Namespace.current() ) 213 melop.doIt() 214 215 return self
216 217 #} END edit methods 218
219 - def parent( self ):
220 """:return: parent namespace of this instance""" 221 if self == self.rootpath: 222 return None 223 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 )
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 ) ): # returns root-relative names ! 236 if ns in self._defaultns or not predicate( ns ): 237 continue 238 out.append( self.__class__( ns ) ) 239 # END for each subspace 240 lastcurrent.setCurrent( ) 241 242 return out
243 244 #{Query Methods 245 246 @classmethod
247 - def current( cls ):
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 253 return cls( nsname )
254 255 @classmethod
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. 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 # END while loop 273 raise AssertionError("Should never get here")
274
275 - def exists( self ):
276 """:return: True if this namespace exists""" 277 return cmds.namespace( ex=self )
278
279 - def isAbsolute( self ):
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
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 292 293 return self.__class__( self[1:], absolute=False )
294
295 - def relativeTo( self, basenamespace ):
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
316 - def splitNamespace( cls, objectname ):
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
330 - def _removeDuplicateSep( self, name ):
331 """:return: name with duplicated : removed""" 332 return self.re_find_duplicate_sep.sub( self._sep, name )
333
334 - def substitute( self, find_in, replacement ):
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 # special case : we are root 346 if self == Namespace.rootpath: 347 return self._removeDuplicateSep( self.__class__( replacement, absolute=False ) + find_in ) 348 349 # do the replacement 350 return self._removeDuplicateSep( find_in.replace( self, replacement ) )
351 352 @classmethod
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 )
356 357 #} END query methods 358 359 360 #{ Object Retrieval 361
362 - def iterNodes( self, *args, **kwargs ):
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 # we handle node conversion 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 # check depth 412 if depth > -1: 413 ns = Namespace(ns) 414 if self == ns: # its depth 0 415 return True 416 417 # one separator means two subpaths 418 if nsGetRelativeTo(ns, self).count(':')+1 > depth: 419 return False 420 # END handle depth 421 return True
422 # END filter 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 # END first namespace check 438 439 # duplicated to be as fast as possible 440 # check depth 441 if depth > -1: 442 ns = Namespace(ns) 443 if self == ns: # its depth 0 444 return True 445 446 if nsGetRelativeTo(ns, self).count(':')+1 > depth: 447 return False 448 # END handle depth 449 return True 450 # END filter 451 iter_type = nt.it.iterDgNodes 452 pred = check_filter 453 # END dag handling 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 # END for each object to yield
464 #} END object retrieval 465 466 467 #{ Static Access 468 -def createNamespace( *args ):
469 """see `Namespace.create`""" 470 return Namespace.create( *args )
471
472 -def currentNamespace( ):
473 """see `Namespace.current`""" 474 return Namespace.current()
475
476 -def findUniqueNamespace( *args, **kwargs ):
477 """see `Namespace.findUnique`""" 478 return Namespace.findUnique( *args, **kwargs )
479
480 -def existsNamespace( namespace ):
481 """:return : True if given namespace ( name ) exists""" 482 return Namespace( namespace ).exists()
483 484 485 RootNamespace = Namespace(Namespace.rootpath) 486 487 #} END Static Access 488