Descriptors and pythonic Maya properties
Posted on Tue 11 March 2014 in blog
I’m still working on the followup to Rescuing Maya GUI From Itself, but while I was at it this StackOverflow question made me realize that the same trick works for pyMel-style property access to things like position or rotation. If you’re a member of the anti-pyMel brigade you might find this a useful trick for things like pCube1.translation = (0,10,0)
. Personally I use pyMel most of the time, but this is a good supplement or alternative for haterz or for special circumstance where pymel is too heavy.
The goal is to be able to write something like
from xform import Xform
example = Xform('pCube1')
print example.translation
# [0,0,0]
example.rotation = (0,40, 0)
The process is about as simple as it can get thanks to the magic of descriptors. This example spotlights one advantage of descriptors over getter/setter property functions: by inheriting the two classes (BBoxProperty
and WorldXformProperty
) I can get 4 distinct behaviors (world and local, read-write and read-only) with very little code and no if-checks.
'''
xform.py
Exposes the xform class: a simple way to set maya position, rotation and similar properties with point notation.
(c) 2014 Steve Theodore. Distributed under the MIT License (http://opensource.org/licenses/MIT)
TLDR: Use, change and share, please retain this copyright notice.
'''
import maya.cmds as cmds
class XformProperty( object ):
CMD = cmds.xform
'''
Descriptor that allows for get-set access of transform properties
'''
def __init__( self, flag ):
self.Flag = flag
self._q_args = {'q':True, flag:True}
self._e_args = {flag: 0}
def __get__( self, obj, objtype ):
return self.CMD( obj, **self._q_args )
def __set__( self, obj, value ):
self._e_args[self.Flag] = value
self.CMD( obj, **self._e_args )
class WorldXformProperty( XformProperty ):
'''
Get-set property in world space
'''
def __init__( self, flag ):
self.Flag = flag
self._q_args = {'q':True, flag:True, 'ws':True}
self._e_args = {flag: 0, 'ws':True}
class BBoxProperty ( XformProperty ):
'''
Read only property for bounding boxes
'''
def __set__( self, obj, value ):
raise RuntimeError ( "bounding box is a read-only property!" )
class WorldBBoxProperty ( WorldXformProperty, BBoxProperty ):
'''
Read only property for bounding boxes
'''
pass
class Xform( object ):
'''
Thin wrapper providing point-notation access to transform attributes
example = Xform('pCube1')
# |pCube1
example.translation
# [0,0,0]
example.translation = [0,10,0]
For most purposes Xforms are just Maya unicode object names. Note this does
NOT track name changes automatically. You can, however, use 'rename':
example = Xform('pCube1')
example.rename('fred')
print example.Object
# |fred
'''
def __init__( self, obj ):
self.Object = cmds.ls( obj, l=True )[0]
def __repr__( self ):
return unicode( self.Object ) # so that the command will work on the string name of the object
# property descriptors These are descriptors so they live at the class level,
# not inside __init__!
translation = XformProperty( 'translation' )
rotation = XformProperty( 'rotation' )
scale = XformProperty( 'scale' )
pivots = XformProperty( 'pivots' )
world_translation = WorldXformProperty( 'translation' )
world_rotation = WorldXformProperty( 'rotation' )
world_pivots = WorldXformProperty( 'pivots' )
# maya does not allow 'world scale' - it's dependent on the parent scale
# always local
scaleTranslation = XformProperty( 'scaleTranslation' )
rotateTranslation = XformProperty( 'rotateTranslation' )
boundingBox = BBoxProperty( 'boundingBox' )
world_boundingBox = WorldBBoxProperty( 'boundingBox' )
def rename( self, new_name ):
self.Object = cmds.ls( cmds.rename( self.Object, new_name ), l=True )[0]
@classmethod
def ls( cls, *args, **kwargs ):
'''
Returns a list of Xforms, using the same arguments and flags as the default ls command
'''
try:
nodes = cmds.ls( *cmds.ls( *args, **kwargs ), type='transform' )
return map ( Xform, nodes )
except:
return []
You may note the absence of a __metaclass__
. In this case, with only a single class, a meta would be an unnecessary complication. Meanwhile the code for MayaGUI itself is up on GitHub. Comments and/or contributions welcome!