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

Leave a Reply

Your email address will not be published.