from maya import cmds import vec reload(vec) class NBaseVec(object): """ This is the abstract class for the vectors definition """ def __init__(self, attribute_name, base_name="Vector"): """ This is the constructor The constructor is in charge to store the input data and it will also automatically create a name generator used internally to name nodes Args: :attribute_name: str, the name of the attribute that will be used as vector source :base_name: str, the base name used for the generated nodes """ #Store the data self.attribute_name= attribute_name self._generator = None #Initialize the internal generator self.init_name_generator(base_name) def init_name_generator(self, base_name): """Initialize the internal generator This function is in charge to create the name generator used for generating the names used for the nodes. Args: :base_name: str, the base name used to init the generator """ self._generator = (base_name + "%03d" % i for i in range(1, 9999)) def set_generator(self, generator): """ Function that sets in the class the generator we wish to use Args: :generator: generator, existing generator for the names """ self._generator = generator def connect_to(self, target_attr): """ Simple function that connects the internal attribute to the target attribute Args: :target_attr: str, the attribute to connect to """ cmds.connectAttr(self.attribute_name , target_attr) def as_list(self): """ This function returns the value of the vector as a list :return: list """ attr = cmds.getAttr(self.attribute_name) if not isinstance(attr,list) : return [attr] else: if isinstance(attr[0], tuple): return list(attr[0]) else: return attr def as_vec(self): """ This function returns the value of the vector as a Vec class :return: Vec instance """ return vec.Vec(self.as_list()) def _generate_node(self, node_type, suffix): """ Generic function for generating nodes named properly Args: :node_type: str, the type of the node we want to create :suffix: str, the suffix for the node """ return cmds.createNode(node_type, n = self._generator.next()+ "_" + suffix) class NScalar(NBaseVec): """ This class represents a single float number used for vector operation, in pratice it reflects a VEC1 (vec of length one) that is needed in some vector operation, like scalar value operation """ def __init__(self, attribute_name, base_name="Vector"): """ This is the constructor Args: :attribute_name: the name of the attribute that will be used as vector source :base_name: the base name used for the generated nodes """ NBaseVec.__init__(self,attribute_name,base_name="Vector") @classmethod def with_generator(cls, attribute_name, generator): """Alternative constructor from an attribute and an existing generator Args: :attribute_name: str, the name of the attribute that will be used as vector source :generator: generator, existing generator for the names :return: nVec instance """ instance = cls(attribute_name) instance.set_generator(generator) return instance @classmethod def from_value(cls, value, base_name): """ Generating a scalar vector from a value This function generates a node and a channel used to host the value and attach the channel to a NScalar vector class Args: :value: float,int, the value of the NScalar :base_name: str, the name we will use for the node + "_vec", the attribute name will be generated with base_name + "_from_value" """ node = cmds.createNode("transform", n= base_name + '_vec') attr_name = base_name + "_from_value" cmds.addAttr(node, ln = attr_name, at="float", k=1) cmds.setAttr(node + '.' + attr_name, value) return cls(node + '.' + attr_name , base_name) def scalar_dynamic(self, scalB): """ The dynamic mult for the NScalar class By dynamic I mean that the scalB value is going to be driven by a connection, thus needs to be a NScalar instance. :scalB: NScalar, the scalar to be multiplied by :return: NScalar instance """ md = self._generate_node("multDoubleLinear", "mul") cmds.connectAttr(self.attribute_name, md + '.input1') cmds.connectAttr(scalB.attribute_name, md + '.input2') return NScalar.with_generator(md + ".output", self._generator) def scalar_static(self, value): """ The static mult for the NScalar class By static I mean that the scalB value is going to hardcoded in the node performing the operation, which means it wont change over time :value: int,float, the value to be multiplied by :return: NScalar instance """ md = self._generate_node("multDoubleLinear", "mul") cmds.connectAttr(self.attribute_name, md + '.input1') cmds.setAttr(md + '.input2',value) return NScalar.with_generator(md + ".output", self._generator) def division_dynamic(self, scalB): """ The dynamic divison for the NScalar class By dynamic I mean that the scalB value is going to be driven by a connection, thus needs to be a NScalar instance. :scalB: NScalar, the scalar to be divided by :return: NScalar instance """ md = self._generate_node("multiplyDivide", "div") cmds.connectAttr(self.attribute_name, md + '.input1X') cmds.connectAttr(scalB.attribute_name, md + '.input2X') cmds.setAttr(md + '.operation',2) return NScalar.with_generator(md + ".outputX", self._generator) def division_static(self,value): """ The static divison for the NScalar class By static I mean that the scalB value is going to be static and hardcoded, in the node. Meant for value not changing over time Args: :value: float,int, the value needed for the operation :return: NScalar instance """ md = self._generate_node("multiplyDivide", "div") cmds.connectAttr(self.attribute_name, md + '.input1X') cmds.setAttr(md + '.input2X', value) cmds.setAttr(md + '.operation',2) return NScalar.with_generator(md + ".outputX", self._generator) def blend(self, scalB, drv): """ Blending function of two scalars This function is used to blend two different scalar where the current scalar will be at the 0 size of the blend , instead the provided scalar will be to the 1 side of the blend Args: :scalB: NScalar, the instance of the scalar to blend with :drv: NScalar, an instance of scalar used to drive the blend """ bln = self._generate_node("blendColors", "blen") cmds.connectAttr(scalB.attribute_name, bln + '.color1R') cmds.connectAttr(self.attribute_name, bln + '.color2R') cmds.connectAttr(drv.attribute_name , bln + '.blender') return NScalar.with_generator(bln + ".outputR", self._generator) def __add__(self,scalB): """ addition operator for NScalar class Args: :scalB: NScalar, the second class for the operation :return: NScalar instance """ pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub") cmds.connectAttr(scalB.attribute_name, pma + '.input1D[0]') cmds.connectAttr(self.attribute_name, pma + '.input1D[1]') return NScalar.with_generator(pma+ '.output1D', self._generator) def __sub__(self,scalB): """ subtraciton operator for NScalar class Args: :scalB: NScalar, the second class for the operation :return: NScalar instance """ pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub") cmds.setAttr(pma + '.operation',2) cmds.connectAttr(scalB.attribute_name, pma + '.input1D[0]') cmds.connectAttr(self.attribute_name, pma + '.input1D[1]') return NScalar.with_generator(pma+ '.output1D', self._generator) def __mul__( self, scalB): """ The mult operator for scalars :scalB: NScalar, the scalar to be multiplied with :return: NScalar instance """ return self.scalar_dynamic(scalB) def __div__(self, scalB): """ The division operator for scalars :scalB: NScalar, the scalar to be divided by :return: NScalar instance """ return self.division_dynamic(scalB) def __rmul__(self, value): """ Right multiplication operator for NScalar class This function gets called in the case we get a float,int multiplied by a NScalar instance and it performs a scalar_static operation Args: :value: float,int, the value to scale the vector by :return: NVec instance """ return self.scalar_static(value) def __rdiv__(self, value): """ Right division operator for NScalar class This function gets called in the case we get a float,int divided by a NScalar instance and it performs a scalar_static operation Args: :value: float,int, the value to divide the vector by :return: NVec instance """ return self.division_static(value) class NVec(NBaseVec): """ This class lets easily perform vector operation as node based maya operation """ def __init__(self, attribute_name, base_name="Vector"): """This is the constructor Args: :attribute_name: str, the name of the attribute that will be used as \ vector source :base_name: the base name used for the generated nodes """ NBaseVec.__init__(self,attribute_name,base_name="Vector") @classmethod def with_generator(cls, attribute_name, generator): """Alternative constructor from an attribute and an existing generator Args: :attribute_name: str, the name of the attribute that will be used as vector source :generator: generator, existing generator for the names :return: nVec instance """ instance = cls(attribute_name) instance.set_generator(generator) return instance def length(self): """ This function computes the length of the vector :return: NVec instance """ dist = cmds.createNode("distanceBetween", n= self._generator.next()+"_length") cmds.connectAttr(self.attribute_name, dist + '.point2') return NScalar.with_generator(dist+ '.distance', self._generator) def normalize(self): """ This function normalize the vector :return: NVec instance """ norm = cmds.createNode("vectorProduct", n = self._generator.next()+ "_norm") cmds.connectAttr(self.attribute_name, norm + '.input1') cmds.setAttr(norm + '.operation', 0) cmds.setAttr(norm + '.normalizeOutput',1) return NVec.with_generator(norm+ '.output', self._generator) def scalar_dynamic (self, scal): ''' This function performs a scalar multiplication This function is called dynamic because it uses a connection to get the value for the multiplication, in short it is a NScalar instance (aka a vec1) that gets used three times to multiply the current vector :scal: NScalar, a scalar class instance :return: NVec instance ''' assert type(scal) == NScalar, "__sub__ ERROR: input parameter needs to be of type NScalar" mult = cmds.createNode("multiplyDivide", n= self._generator.next()+ '_scalarDyn') cmds.connectAttr(scal.attribute_name , mult + '.input2X') cmds.connectAttr(scal.attribute_name , mult + '.input2Y') cmds.connectAttr(scal.attribute_name , mult + '.input2Z') cmds.connectAttr(self.attribute_name, mult + '.input1') return NVec.with_generator(mult+ '.output', self._generator) def scalar_static(self, value): """ This is a static scalar multiplication of the vector By static it means it multiplies by a fixed value which is not dynamic like an attribute connection Args: :value: float,int, the value for which we wist to scale the vector for :return: NVec instance """ mult = cmds.createNode("multiplyDivide", n= self._generator.next()+ '_scalarStatic') cmds.setAttr(mult + '.input2X',value) cmds.setAttr(mult + '.input2Y',value) cmds.setAttr(mult + '.input2Z',value) cmds.connectAttr(self.attribute_name, mult + '.input1') return NVec.with_generator(mult+ '.output', self._generator) def dot(self, vecB): """ This function performs a dot product Args: :vecB: NVec, the second class for the operation :return: NVec instance """ assert type(vecB) == NVec, "__sub__ ERROR: input parameter needs to be of type NVec" vecProd = cmds.createNode("vectorProduct", n = self._generator.next()+ "_dot") cmds.connectAttr(self.attribute_name , vecProd + '.input1') cmds.connectAttr(vecB.attribute_name , vecProd + '.input2') return NVec.with_generator(vecProd+ '.output', self._generator) def cross(self,vecB): """ Cross product function Args: :vecB: NVec, the second class for the operation :return: NVec instance """ assert type(vecB) == NVec, "__sub__ ERROR: input parameter needs to be of type NVec" vecProd = cmds.createNode("vectorProduct", n = self._generator.next()+ "_cross") cmds.setAttr(vecProd + '.operation', 2) cmds.connectAttr(self.attribute_name , vecProd + '.input1') cmds.connectAttr(vecB.attribute_name , vecProd + '.input2') return NVec.with_generator(vecProd+ '.output', self._generator) def __len__(self): """ Length operator for the nVec class :return: NVec instance """ return self.length() def __sub__(self,vecB): """ Subtraction operator for NVec Args: :vecB: NVec, the second class for the operation :return: NVec instance """ assert type(vecB) == NVec, "__sub__ ERROR: input parameter needs to be of type NVec" pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub") cmds.connectAttr(vecB.attribute_name, pma + '.input3D[0]') cmds.connectAttr(self.attribute_name, pma + '.input3D[1]') cmds.setAttr(pma + '.operation',2) return NVec.with_generator(pma+ '.output3D', self._generator) def __xor__(self, vecB): """ Cross product operator Args: :vecB: NVec, the second class for the operation :return: NVec instance """ return self.cross(vecB) def __add__(self, vecB): """ addition operator for NVec class Args: :vecB: NVec, the second class for the operation :return: NVec instance """ pma = cmds.createNode("plusMinusAverage", n = self._generator.next()+ "_sub") cmds.connectAttr(vecB.attribute_name, pma + '.input3D[0]') cmds.connectAttr(self.attribute_name, pma + '.input3D[1]') return NVec.with_generator(pma+ '.output3D', self._generator) def __neg__(self): """ Negation operator for the NVec class """ return self.scalar_static(-1.0) def __mul__(self, vecB): """ multiplication operator for NVec class Args: :vecB: NVec, the second class for the operation :return: NVec instance """ return self.dot(vecB) def __rmul__(self,value): """ Right multiplication operator for NVec class This function gets called in the case we get a float,int multiplied by a NVec instance and it performs a static_scalar operation Args: :value: float,int, the value to scale the vector by :return: NVec instance """ return self.scalar_static(value) """ #perpendicular import sys sys.path.append("/user_data/WORK_IN_PROGRESS/mVec/") import nVec reload(nVec) locBase = "loc_base.worldPosition" locA = "locA_tip.worldPosition" locB = "locB_tip.worldPosition" target = "target_loc.t" scalar = "scalar_loc.ty" vecBase = nVec.NVec(locBase,"test") vecA = nVec.NVec(locA,"test") vecB = nVec.NVec(locB,"test") scalV = nVec.NVec(scalar,"scal") vec1 = vecA - vecBase vec2 = vecB - vecBase cross = vec1 ^ vec2 norm = cross.normalize() final = vecBase + (2*(-norm).scalar_dynamic(scalV)) final.connect_to(target) """ """ import sys sys.path.append("/user_data/WORK_IN_PROGRESS/mVec/") from maya import cmds import nVec reload(nVec) locBase = "loc_base.worldPosition" locA = "locA_tip.worldPosition" locB = "locB_tip.worldPosition" target = "target_loc.t" scalar = "scalar_loc.ty" vecBase = nVec.NVec(locBase,"test") vecA = nVec.NVec(locA,"test") vecB = nVec.NVec(locB,"test") scalV = nVec.NScalar(scalar,"scal") vec1 = vecA - vecBase vec2 = vecB - vecBase cross = vec1 ^ vec2 norm = cross.normalize() final = vecBase + (2*(-norm).scalar_dynamic(scalV)) final.connect_to(target) print final.as_list() import vec reload(vec) locBaseL = cmds.getAttr("loc_base.worldPosition")[0] locAL = cmds.getAttr("locA_tip.worldPosition")[0] locBL = cmds.getAttr("locB_tip.worldPosition")[0] scalV = cmds.getAttr("scalar_loc.ty") vecBase = vec.Vec(locBaseL) vecA = vec.Vec(locAL) vecB = vec.Vec(locBL) vec1 = vecA - vecBase vec2 = vecB - vecBase cross = vec1 ^ vec2 norm = cross.normalize() final = vecBase + (scalV*2*(-norm)) print final.as_list() """ """ #basic stretch IK from maya import cmds import nVec reload(nVec) #declaring initial vectors startV = nVec.NVec("start_drv.worldPosition", "sStretch") endV = nVec.NVec("end_drv.worldPosition", "eStretch") poleV = nVec.NVec("poleVec_drv.worldPosition", "pStretch") stretchV= nVec.NScalar("end_drv.stretch","stretch") lockV= nVec.NScalar("end_drv.lock","lock") #computing the length between the end and the start of the chain distV = endV - startV length = distV.length() #getting initial chain length and converting into vectors upLen = cmds.getAttr(chain[1] + '.tx') lowLen = cmds.getAttr(chain[2] + '.tx') upLenV = nVec.NScalar.from_value(upLen, "upLen") lowLenV = nVec.NScalar.from_value(lowLen, "lowLen") #getting total length chain (this can be easily multiplied by the global scale) initLen = upLenV+lowLenV #finding theratio ratio = length /initLen #calculating scaled length scaledUp = upLenV * ratio scaledlow = lowLenV * ratio #computing final blended stretch finalScaledUp = upLenV.blend(scaledUp, stretchV) finalScaledLow = lowLenV.blend(scaledlow,stretchV) #condition node (old school) cnd = cmds.createNode("condition") ratio.connect_to(cnd + '.firstTerm') cmds.setAttr(cnd + '.secondTerm' ,1) cmds.setAttr(cnd + '.operation', 3) #connecting our final calculaded stretch node to the cnd colors finalScaledUp.connect_to(cnd + '.colorIfTrueR') upLenV.connect_to(cnd + '.colorIfFalseR') finalScaledLow.connect_to(cnd + '.colorIfTrueG') lowLenV.connect_to(cnd + '.colorIfFalseG') cmds.connectAttr(cnd + '.outColorR', chain[1] + '.tx') cmds.connectAttr(cnd + '.outColorG', chain[2] + '.tx') """ """ #ADVANCED STRETCH from maya import cmds import nVec reload(nVec) #declaring initial vectors startV = nVec.NVec("start_drv.worldPosition", "sStretch") endV = nVec.NVec("end_drv.worldPosition", "eStretch") poleV = nVec.NVec("poleVec_drv.worldPosition", "pStretch") stretchV= nVec.NScalar("end_drv.stretch","stretch") lockV= nVec.NScalar("end_drv.lock","lock") #computing the length between the end and the start of the chain distV = endV - startV length = distV.length() #getting initial chain length and converting into vectors upLen = cmds.getAttr(chain[1] + '.tx') lowLen = cmds.getAttr(chain[2] + '.tx') upLenV = nVec.NScalar.from_value(upLen, "upLen") lowLenV = nVec.NScalar.from_value(lowLen, "lowLen") #getting total length chain (this can be easily multiplied by the global scale) initLen = upLenV+lowLenV #finding theratio ratio = length /initLen #calculating scaled length scaledUp = upLenV * ratio scaledlow = lowLenV * ratio #computing final blended stretch finalScaledUp = upLenV.blend(scaledUp, stretchV) finalScaledLow = lowLenV.blend(scaledlow,stretchV) #condition node (old school) cnd = cmds.createNode("condition") ratio.connect_to(cnd + '.firstTerm') cmds.setAttr(cnd + '.secondTerm' ,1) cmds.setAttr(cnd + '.operation', 3) #connecting our final calculaded stretch node to the cnd colors finalScaledUp.connect_to(cnd + '.colorIfTrueR') upLenV.connect_to(cnd + '.colorIfFalseR') finalScaledLow.connect_to(cnd + '.colorIfTrueG') lowLenV.connect_to(cnd + '.colorIfFalseG') #now compute the pole vector lock #get polevec vectors upPoleVec = poleV - startV lowPoleVec = poleV - endV #computing the length upPoleLen = upPoleVec.length() lowPoleLen= lowPoleVec.length() #blending default length with poleVec vectors upPoleBlen = upLenV.blend(upPoleLen, lockV) lowPoleBlen = lowLenV.blend(lowPoleLen, lockV) #connecting a NScalar to the output of the node finalStrUp = nVec.NScalar(cnd + '.outColorR') finalStrLow = nVec.NScalar(cnd + '.outColorG') #blending the stretch and lock lengths resUp = finalStrUp.blend(upPoleBlen,lockV) resLow =finalStrLow.blend(lowPoleBlen,lockV) #connect final result resUp.connect_to(chain[1] + '.tx') resLow.connect_to(chain[2] + '.tx') """