bidirectional script <-> maya transform in python

I thought it might be interesting to have a two-way connection between an object in maya and script. What do I mean by this? Well, imagine that you create a transform, joint, or locator in script and update some of its attributes, and naturally, the change is reflected in the 3d view; but then you drag the object around in the 3d view, and now the changes are reflected in the instance in script.

Here is an example of the usage:


transform= c_transform(name= "test_transform", translate= [1.0,0.0,0.0])
# change the rotation
transform.rx= 90.0 

# now grab the transform and translate or rotate it.

# then check the translation or rotations
print(transform.translate)
print(transform.rotate)

# you can also access by attr
transform.tx= 5.0

Here is the actual code for this. Note that the transform can be parented under another object and its attributes will be correctly updated. With all of the callbacks for attrs changing and parenting and such, as well as the branchy/fugly attr checking, this code is really slow so I wouldn’t recommend using this to create tons of objects. This was mostly on a whim anyhow, and not for real production.

import maya.cmds as cmds
import maya.OpenMaya as om
import math

class c_transform(object):
    tx=0
    ty=0
    tz=0
    
    rx=0
    ry=0
    rz=0
    
    sx=1
    sy=1
    sz=1
    
    v=True
    
    name= None
    translate= []
    rotate= []
    scale= []
    visibility=True
    
    set_from_script= False  
    
    on_attr_changed_id= None
    on_node_destroyed_id= None
    on_parent_added_callback_id= None
    on_name_changed_callback_id= None
    
    def __init__(self, *args, **kwargs):
        self.set_from_script= True
        
        self.tx= 0
        self.ty= 0
        self.tz= 0

        self.translate= [self.tx,self.ty,self.tz]

        self.rx= 0
        self.ry= 0
        self.rz= 0

        self.rotate= [self.rx,self.ry,self.rz]

        self.sx= 1
        self.sy= 1
        self.sz= 1

        self.scale= [self.sx,self.sy,self.sz]

        self.v=True
        self.visibility=True

        self.create(*args, **kwargs)

        if kwargs.has_key('translate') and type(kwargs['translate']) is list:
            self.tx= kwargs['translate'][0]
            self.ty= kwargs['translate'][1]
            self.tz= kwargs['translate'][2]
            self.translate= [self.tx, self.ty, self.tz]
            cmds.setAttr(self.name+'.translateX', self.tx)
            cmds.setAttr(self.name+'.translateY', self.ty)
            cmds.setAttr(self.name+'.translateZ', self.tz)

        if kwargs.has_key('rotate') and type(kwargs['rotate']) is list:
            self.rx= kwargs['rotate'][0]
            self.ry= kwargs['rotate'][1]
            self.rz= kwargs['rotate'][2]
            self.rotate= [self.rx, self.ry, self.rz]
            cmds.setAttr(self.name+'.rotateX', self.rx)
            cmds.setAttr(self.name+'.rotateY', self.ry)
            cmds.setAttr(self.name+'.rotateZ', self.rz)

        if kwargs.has_key('scale') and type(kwargs['scale']) is list:
            self.sx= kwargs['scale'][0]
            self.sy= kwargs['scale'][1]
            self.sz= kwargs['scale'][2]
            self.scale= [self.sx, self.sy, self.sz]
            cmds.setAttr(self.name+'.scaleX', self.sx)
            cmds.setAttr(self.name+'.scaleY', self.sy)
            cmds.setAttr(self.name+'.scaleZ', self.sz)
            
        
        dag_iter= om.MItDag()
        found= False        
        
        while not dag_iter.isDone() and found == False:
            curr= dag_iter.currentItem()
            fn= om.MFnDependencyNode(curr)
            if self.name == fn.name():              
                self.on_attr_changed_id = om.MNodeMessage.addAttributeChangedCallback(curr, self.on_attr_changed)
                self.on_node_destroyed_id= om.MNodeMessage.addNodeDestroyedCallback(curr, self.on_node_destroyed)
                self.on_name_changed_callback_id= om.MNodeMessage.addNameChangedCallback(curr, self.on_name_changed)
                
                dag_path= om.MDagPath()
                om.MDagPath.getAPathTo(curr, dag_path)
                
                self.on_parent_added_callback_id= om.MDagMessage.addParentAddedDagPathCallback(dag_path, self.on_parent_added)
                
                found= True
            dag_iter.next() 
            
    def create(self, *args, **kwargs):
        if kwargs.has_key('name'):          
            self.name= cmds.createNode('transform', n=kwargs['name'])
        elif kwargs.has_key('n'):
            self.name= cmds.createNode('transform', n=kwargs['n'])
        else:
            self.name= cmds.createNode('transform')     
        
    def __del__(self):
        # print("calling __del__")
        om.MMessage.removeCallback(self.on_attr_changed_id)
        om.MMessage.removeCallback(self.on_parent_added_callback_id)
        om.MMessage.removeCallback(self.on_node_destroyed_id)
        om.MMessage.removeCallback(self.on_name_changed_callback_id)
        
    def on_node_destroyed(self, clientData=None):
        print("node destroyed callback")
        self.__del__()
        
    def on_name_changed(self, node, old_name, clientData=None):     
        self.name= new_name
        
    def on_attr_changed(self, msg, plug, other_plug, clientData= None):         
        if msg & om.MNodeMessage.kAttributeSet:         
            if plug.isCompound():
                #print ("Trying to set plug " + plug.partialName())
                for i in range(0,plug.numChildren()):
                    child_plug= plug.child(i)
                    if self.__dict__.has_key(child_plug.partialName()):                 
                        self.set_from_script= False
                        if child_plug.partialName() not in ['rx', 'ry', 'rz']:
                            self.__setattr__(str(child_plug.partialName()), child_plug.asFloat())
                        else:
                            self.__setattr__(str(child_plug.partialName()), math.degrees(child_plug.asFloat()))
                        self.set_from_script= True                  
            else:
                if self.__dict__.has_key(plug.partialName()):
                    #print ("Trying to set plug " + plug.partialName())
                    self.set_from_script= False
                    if plug.partialName() not in ['rx', 'ry', 'rz']:
                        self.__setattr__(str(plug.partialName()), plug.asFloat())
                    else:
                        self.__setattr__(str(plug.partialName()), math.degrees(plug.asFloat()))
                    self.set_from_script= True
                    
    
    def on_parent_added(self, child, parent, clientData= None):
        fnc= om.MFnDependencyNode(child.node())
        fnp= om.MFnDependencyNode(parent.node())        
        tx= self.getAttr('tx')
        ty= self.getAttr('ty')
        tz= self.getAttr('tz')
        rx= self.getAttr('rx')
        ry= self.getAttr('ry')
        rz= self.getAttr('rz')
        sx= self.getAttr('sx')
        sy= self.getAttr('sy')
        sz= self.getAttr('sz')
        v=  self.getAttr('v')       
        
        self.tx= tx
        self.ty= ty
        self.tz= tz
        self.rx= rx
        self.ry= ry
        self.rz= rz
        self.sx= sx
        self.sy= sy
        self.sz= sz
        self.v=  v
        
        
    def getAttr(self, attr):
        return cmds.getAttr(self.name+'.'+attr)

    def __setattr__(self, attr, value):
        #print ("calling __setattr__ ", attr, value)
        
        if attr == 'name':
            if self.name != None:
                if cmds.objExists(self.name) and self.name != value:
                    cmds.rename(self.name, value)               
                    self.__dict__['name']= value
                elif cmds.objExists(value) and self.name != value:
                    self.__dict__['name']= value
            else:
                self.__dict__['name']= value
            return
            
        if attr == 'set_from_script':
            self.__dict__[attr]= value
            return
            
        if attr == 'v':         
            self.__dict__[attr]= value
            self.__dict__['visibility']= self.__dict__['v']
        if attr == 'visibility':
            self.__dict__[attr]= value          
            self.__dict__['v']= self.__dict__['visibility'] 
            
        if self.__dict__.has_key(attr) and attr != "translate" and attr != "rotate" and attr != "scale":
            if self.__dict__[attr] != value:                
                self.__dict__[attr]= value
                
                # clamp super small numbers to 0
                if type(value) is float and (value > 0 and value < 0.00001) or (value < 0 and value > -0.00001):
                    value= 0.0
                    
                if type(value) is list:
                    idx= 0
                    for v in value:
                        if type(v) is float and (v > 0 and v < 0.00001) or (v < 0 and v > -0.00001):
                            value[idx]= 0.0
                        idx= idx+1
                
                if attr == 'tx' or attr == 'ty' or attr == 'tz':
                    if attr == 'tx':                        
                        self.__dict__['translate']= [value, self.ty, self.tz]
                    elif attr == 'ty':
                        self.__dict__['translate']= [self.tx, value, self.tz]
                    elif attr == 'tz':
                        self.__dict__['translate']= [self.tx, self.ty, value]
                elif attr == 'rx' or attr == 'ry' or attr == 'rz':
                    if attr == 'rx':
                        self.__dict__['rotate']= [value, self.ry, self.rz]
                    elif attr == 'ry':
                        self.__dict__['rotate']= [self.rx, value, self.rz]
                    elif attr == 'rz':
                        self.__dict__['rotate']= [self.rx, self.ry, value]
                elif attr == 'sx' or attr == 'sy' or attr == 'sz':
                    if attr == 'sx':
                        self.__dict__['scale']= [value, self.sy, self.sz]
                    elif attr == 'sy':
                        self.__dict__['scale']= [self.sx, value, self.sz]
                    elif attr == 'sz':
                        self.__dict__['scale']= [self.sx, self.sy, value]                           
        elif attr == "translate" and type(value) is list:
            self.__dict__[attr]= value
            self.__dict__['tx']= value[0]
            self.__dict__['ty']= value[1]
            self.__dict__['tz']= value[2]
        elif attr == "rotate" and type(value) is list:
            self.__dict__[attr]= value
            self.__dict__['rx']= value[0]
            self.__dict__['ry']= value[1]
            self.__dict__['rz']= value[2]
        elif attr == "scale" and type(value) is list:
            self.__dict__[attr]= value
            self.__dict__['sx']= value[0]
            self.__dict__['sy']= value[1]
            self.__dict__['sz']= value[2]
            
        if self.set_from_script == True:
            self.update()
        
    def update(self):
        if self.set_from_script == True:
            if self.name != None:           
                cmds.setAttr(self.name+'.translateX', self.tx)
                cmds.setAttr(self.name+'.translateY', self.ty)
                cmds.setAttr(self.name+'.translateZ', self.tz)
                cmds.setAttr(self.name+'.rotateX', self.rx)
                cmds.setAttr(self.name+'.rotateY', self.ry)
                cmds.setAttr(self.name+'.rotateZ', self.rz)
                cmds.setAttr(self.name+'.scaleX', self.sx)
                cmds.setAttr(self.name+'.scaleY', self.sy)
                cmds.setAttr(self.name+'.scaleZ', self.sz)      
                cmds.setAttr(self.name+'.visibility', self.v)
            
    def reset(self):
        self.translate= [0,0,0]
        self.rotate= [0,0,0]
        self.scale= [1,1,1]
        self.visibility= True

simple templated bit flags class example

The inaugural post in this category is a fun little example–a simple templated bit flags class.
Sometimes, it is nice to be able to index into a bitvector with an enumeration. Take a look at this example:

// No more than size of 32 bits
enum e_item_flags
{
    _item_flags_single_use,	
    _item_flags_placed_in_world,
    _item_flags_can_be_traded,
    _item_flags_causes_effect_on_use,
    _item_flags_can_respawn,
    k_item_flags_count
};

typedef c_flags<e_item_flags, k_item_flags_count> c_item_flags; 

c_item_flags item_flags;

...  

item_flags.set( _item_flags_single_use, false );
item_flags.set( _item_flags_placed_in_world, true);
item_flags.set( _item_flags_can_be_traded, true);

if ( item_flags.test(_item_flags_can_be_traded) )
{
    // code to trade item
    ...
}

Here we have wrapped a bitvector class as a templated flags class with an enum as the template parameter, and then typedef’d it to make it a little more readable. Finally, an example of the actual usage is shown. We are pretending that we have an ‘item’ class in a game, and we have a bunch of flags that specify some properties of the item. In this case, we are going to write the code to handle trading the item, so we’ll need to test the bit specifying whether the item in question can be traded. The whole point of this little example is that we can use the enum we defined to index into the bitvector to set or test bits. Simple and sweet. What does the code for the templated flags class look like? Take a look at the code below (flags.h):

template <typename t_enum, int max_count>
class c_flags
{
public:
    c_flags<t_enum, max_count>( void );
   ~c_flags<t_enum, max_count>( void );

    void set( t_enum, bool value );
    bool test( t_enum );
    void clear( void );

private:
    c_bitvector32 bitvector;
};

template <typename t_enum, int max_count>
c_flags<t_enum, max_count>::c_flags( void )
{
    bitvector.resize( max_count );
}

template <typename t_enum, int max_count>
c_flags<t_enum, max_count>:: ~c_flags( void )
{
    clear();
}

template<typename t_enum, int max_count>
void c_flags<t_enum, max_count>::set( t_enum T, bool value )
{
    bitvector.set( T, value );
}

template<typename t_enum, int max_count>
bool c_flags<t_enum, max_count>::test( t_enum T )
{
    return bitvector.test( T );
}

template<typename t_enum, int max_count>
void c_flags<t_enum, max_count>::clear( void )
{
    bitvector.clear_all();
}

I’m not going to go over the code for the bitvector itself, but it is pretty simple and I’ve included it at the bottom of this post. One shortcoming is that it only supports 32 bits. This is easily extended, however, so I’ll leave that to you. Furthermore, it would be nice to extend the bitvector to allow serialization (reading/writing to a file.)

example code

game development blog