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