def _get_follicle_absolute_uv_attr(self, mult_u=1.0, mult_v=1.0): """ Resolve the absolute parameterU and parameterV that will be sent to the follicles. :param mult_u: Custom multiplier :param mult_v: :return: A tuple containing two pymel.Attribute: the absolute parameterU and relative parameterV. """ # TODO: Move attribute definition outside this function. attr_u_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U) attr_v_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V) attr_u_relative, attr_v_relative = self._get_follicle_relative_uv_attr( mult_u=mult_u, mult_v=mult_v) # Add base parameterU & parameterV attr_u_cur = libRigging.create_utility_node( 'addDoubleLinear', input1=self._attr_u_base, input2=attr_u_relative).output attr_v_cur = libRigging.create_utility_node( 'addDoubleLinear', input1=self._attr_v_base, input2=attr_v_relative).output # TODO: Move attribute connection outside of this function. pymel.connectAttr(attr_u_cur, attr_u_inn) pymel.connectAttr(attr_v_cur, attr_v_inn) return attr_u_inn, attr_v_inn
def _get_follicle_absolute_uv_attr(self, mult_u=1.0, mult_v=1.0): """ Resolve the absolute parameterU and parameterV that will be sent to the follicles. :param mult_u: Custom multiplier :param mult_v: :return: A tuple containing two pymel.Attribute: the absolute parameterU and relative parameterV. """ # TODO: Move attribute definition outside this function. attr_u_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U) attr_v_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V) attr_u_relative, attr_v_relative = self._get_follicle_relative_uv_attr(mult_u=mult_u, mult_v=mult_v) # Add base parameterU & parameterV attr_u_cur = libRigging.create_utility_node( 'addDoubleLinear', input1=self._attr_u_base, input2=attr_u_relative ).output attr_v_cur = libRigging.create_utility_node( 'addDoubleLinear', input1=self._attr_v_base, input2=attr_v_relative ).output # TODO: Move attribute connection outside of this function. pymel.connectAttr(attr_u_cur, attr_u_inn) pymel.connectAttr(attr_v_cur, attr_v_inn) return attr_u_inn, attr_v_inn
def setup_softik(self, ik_handle_to_constraint, stretch_chains): """ Setup the softik system a ik system :param ik_handle_to_constraint: A list of ik handles to constraint on the soft-ik network. :param stretch_chains: A list of chains to connect the stretch to. :return: Nothing """ nomenclature_rig = self.get_nomenclature_rig() oAttHolder = self.ctrl_ik fnAddAttr = functools.partial(libAttr.addAttr, hasMinValue=True, hasMaxValue=True) attInRatio = fnAddAttr(oAttHolder, longName='softIkRatio', niceName='SoftIK', defaultValue=0, minValue=0, maxValue=50, k=True) attInStretch = fnAddAttr(oAttHolder, longName='stretch', niceName='Stretch', defaultValue=0, minValue=0, maxValue=1.0, k=True) # Adjust the ratio in percentage so animators understand that 0.03 is 3% attInRatio = libRigging.create_utility_node('multiplyDivide', input1X=attInRatio, input2X=0.01).outputX # Create and configure SoftIK solver soft_ik_network_name = nomenclature_rig.resolve('softik') soft_ik_network = SoftIkNode() soft_ik_network.build(name=soft_ik_network_name) soft_ik_network.setParent(self.grp_rig) pymel.connectAttr(attInRatio, soft_ik_network.inRatio) pymel.connectAttr(attInStretch, soft_ik_network.inStretch) pymel.connectAttr(self._ikChainGrp.worldMatrix, soft_ik_network.inMatrixS) pymel.connectAttr(self._ik_handle_target.worldMatrix, soft_ik_network.inMatrixE) attr_distance = libFormula.parse('distance*globalScale', distance=self.chain_length, globalScale=self.grp_rig.globalScale) pymel.connectAttr(attr_distance, soft_ik_network.inChainLength) attOutRatio = soft_ik_network.outRatio attOutRatioInv = libRigging.create_utility_node('reverse', inputX=soft_ik_network.outRatio).outputX # TODO: Improve softik ratio when using multiple ik handle. Not the same ratio will be used depending of the angle for handle in ik_handle_to_constraint: pointConstraint = pymel.pointConstraint(self._ik_handle_target, self._ikChainGrp, handle) pointConstraint.rename(pointConstraint.stripNamespace().replace('pointConstraint', 'softIkConstraint')) weight_inn, weight_out = pointConstraint.getWeightAliasList()[-2:] # Ensure to get the latest target added pymel.connectAttr(attOutRatio, weight_inn) pymel.connectAttr(attOutRatioInv, weight_out) # Connect stretch for stretch_chain in stretch_chains: for i in range(1, self.iCtrlIndex + 1): obj = stretch_chain[i] util_get_t = libRigging.create_utility_node( 'multiplyDivide', input1X=soft_ik_network.outStretch, input1Y=soft_ik_network.outStretch, input1Z=soft_ik_network.outStretch, input2=obj.t.get() ) pymel.connectAttr(util_get_t.outputX, obj.tx, force=True) pymel.connectAttr(util_get_t.outputY, obj.ty, force=True) pymel.connectAttr(util_get_t.outputZ, obj.tz, force=True) return soft_ik_network
def build(self, *args, **kwargs): super(AdditiveFK, self).build(*args, **kwargs) nomenclature_anm = self.get_nomenclature_anm() # Ensure to create the finger ctrl in the good orientation if nomenclature_anm.side == self.rig.nomenclature.SIDE_L: normal_data = { constants.Axis.x: (1, 0, 0), constants.Axis.y: (0, 1, 0), constants.Axis.z: (0, 0, 1) } else: normal_data = { constants.Axis.x: (-1, 0, 0), constants.Axis.y: (0, -1, 0), constants.Axis.z: (0, 0, -1) } # TODO: Support multiple additive ctrls # TODO: Rename self.additive_ctrls = filter(None, self.additive_ctrls) if not self.additive_ctrls: ctrl_add = CtrlFkAdd() self.additive_ctrls.append(ctrl_add) #HACK - Temp since we don't support multiple ctrl for the moment ctrl_add = self.additive_ctrls[0] for i, ctrl in enumerate(self.additive_ctrls): name = nomenclature_anm.resolve("addFk{0:02d}".format(i)) ctrl.build(name=name, refs=self.chain.start, normal=normal_data[self.rig._up_axis]) ctrl.offset.setMatrix(self.chain.start.getMatrix(worldSpace=True)) ctrl.setParent(self.grp_anm) for i, ctrl in enumerate(self.ctrls): #HACK Add a new layer if this is the first ctrl to prevent Gimbal lock problems if i == 0: ctrl.offset = ctrl.append_layer("gimbal") attr_rotate_x = libRigging.create_utility_node( 'addDoubleLinear', input1=ctrl.offset.rotateX.get(), input2=ctrl_add.rotateX).output attr_rotate_y = libRigging.create_utility_node( 'addDoubleLinear', input1=ctrl.offset.rotateY.get(), input2=ctrl_add.rotateY).output attr_rotate_z = libRigging.create_utility_node( 'addDoubleLinear', input1=ctrl.offset.rotateZ.get(), input2=ctrl_add.rotateZ).output pymel.connectAttr(attr_rotate_x, ctrl.offset.rotateX) pymel.connectAttr(attr_rotate_y, ctrl.offset.rotateY) pymel.connectAttr(attr_rotate_z, ctrl.offset.rotateZ) # Constraint the fk ctrls in position to the additive fk ctrls pymel.pointConstraint(ctrl_add, self.ctrls[0].offset)
def _build_avars(self, **kwargs): nomenclature_rig = self.get_nomenclature_rig() jnt_head = self.get_head_jnt() jnt_jaw = self.get_jaw_jnt() pos_jaw = jnt_jaw.getTranslation(space='world') # # Build a network that evaluate the jaw transform in relation with it's bind pose. # This will be used by avars to computer the 'jawArcT' and 'jawArcR' layer. # self._ref_jaw_predeform = pymel.createNode( 'transform', name=nomenclature_rig.resolve('refJawPreDeform'), parent=self.grp_rig ) self._ref_jaw_predeform.setTranslation(pos_jaw) # Create jaw bind-pose reference ref_head = pymel.createNode( 'transform', name=nomenclature_rig.resolve('refJawBefore'), parent=self.grp_rig ) ref_head.setTranslation(pos_jaw) pymel.parentConstraint(jnt_head, ref_head, maintainOffset=True) # Create jaw actual pose reference ref_jaw = pymel.createNode( 'transform', name=nomenclature_rig.resolve('refJawAfter'), parent=self.grp_rig ) ref_jaw.setTranslation(pos_jaw) pymel.parentConstraint(jnt_jaw, ref_jaw, maintainOffset=True) # Extract the rotation # By using matrix we make sure that there's no flipping introduced. # This happen in maya when using constraint with multiple targets since # Maya use individual values in it's computations. attr_get_relative_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=( ref_jaw.worldMatrix, ref_head.worldInverseMatrix, ) ).matrixSum util_get_rotation_euler = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_get_relative_tm ) self._attr_jaw_pt = util_get_rotation_euler.outputRotateX self._attr_jaw_yw = util_get_rotation_euler.outputRotateY self._attr_jaw_rl = util_get_rotation_euler.outputRotateZ super(FaceLips, self)._build_avars(**kwargs)
def connect(self, avar, avar_grp, ud=True, fb=True, lr=True, yw=True, pt=True, rl=True, sx=True, sy=True, sz=True): need_flip = avar.need_flip_lr() # Position if ud: attr_inn_ud = self.ctrl.translateY libRigging.connectAttr_withBlendWeighted(attr_inn_ud, avar.attr_ud) if lr: attr_inn_lr = self.ctrl.translateX if need_flip: attr_inn_lr = libRigging.create_utility_node('multiplyDivide', input1X=attr_inn_lr, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_lr, avar.attr_lr) if fb: attr_inn_fb = self.ctrl.translateZ libRigging.connectAttr_withBlendWeighted(attr_inn_fb, avar.attr_fb) # Rotation if yw: attr_inn_yw = self.ctrl.rotateY if need_flip: attr_inn_yw = libRigging.create_utility_node('multiplyDivide', input1X=attr_inn_yw, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_yw, avar.attr_yw) if pt: attr_inn_pt = self.ctrl.rotateX libRigging.connectAttr_withBlendWeighted(attr_inn_pt, avar.attr_pt) if rl: attr_inn_rl = self.ctrl.rotateZ if need_flip: attr_inn_rl = libRigging.create_utility_node('multiplyDivide', input1X=attr_inn_rl, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_rl, avar.attr_rl) # Scale if sx: attr_inn = self.ctrl.scaleX libRigging.connectAttr_withBlendWeighted(attr_inn, avar.attr_sx) if sy: attr_inn = self.ctrl.scaleY libRigging.connectAttr_withBlendWeighted(attr_inn, avar.attr_sy) if sz: attr_inn = self.ctrl.scaleZ libRigging.connectAttr_withBlendWeighted(attr_inn, avar.attr_sz)
def _connect_ctrl(self, ctrl, attr_ud=None, attr_lr=None, attr_fb=None, attr_yw=None, attr_pt=None, attr_rl=None): need_flip = self.need_flip_lr() if attr_ud: attr_inn_ud = ctrl.translateY libRigging.connectAttr_withBlendWeighted(attr_inn_ud, attr_ud) if attr_lr: attr_inn_lr = ctrl.translateX if need_flip: attr_inn_lr = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_inn_lr, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_lr, attr_lr) if attr_fb: attr_inn_fb = ctrl.translateZ libRigging.connectAttr_withBlendWeighted(attr_inn_fb, attr_fb) if attr_yw: attr_inn_yw = ctrl.rotateY if need_flip: attr_inn_yw = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_inn_yw, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_yw, attr_yw) if attr_pt: attr_inn_pt = ctrl.rotateX libRigging.connectAttr_withBlendWeighted(attr_inn_pt, attr_pt) if attr_rl: attr_inn_rl = ctrl.rotateZ if need_flip: attr_inn_rl = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_inn_rl, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_rl, attr_rl)
def _init_attr_face_module_visiblity_driver(self): attr_display_ctrl = self._init_attr_display_ctrl() attr_display_mesh = self._init_attr_display_mesh() condition_attrs = { 'operation': 2, # greater than 'firstTerm': attr_display_ctrl, 'secondTerm': attr_display_mesh, 'colorIfTrueR': True, 'colorIfFalseR': False } # Re-use the condition matching the requirements. def _filter(obj): if not isinstance(obj, pymel.nodetypes.Condition): return False for attr_name, attr_val in condition_attrs.iteritems(): attr = obj.attr(attr_name) if attr.isDestination(): if next(iter(attr.inputs(plugs=True)), None) != attr_val: return False else: if attr.get() != attr_val: return False return True condition = next(iter(node for node in attr_display_ctrl.outputs() if _filter(node)), None) if condition is None: condition = libRigging.create_utility_node( 'condition', **condition_attrs ) return condition.outColorR
def _build(self): nomenclature_rig = self.get_nomenclature_rig() grp_output = pymel.createNode( 'transform', name=nomenclature_rig.resolve('output'), parent=self.grp_rig, ) attr_get_t = libRigging.create_utility_node( 'multiplyDivide', input1X=self._attr_inn_lr, input1Y=self._attr_inn_ud, input1Z=self._attr_inn_fb, input2X=self.multiplier_lr, input2Y=self.multiplier_ud, input2Z=self.multiplier_fb, ).output pymel.connectAttr(attr_get_t, grp_output.translate) pymel.connectAttr(self._attr_inn_pt, grp_output.rotateX) pymel.connectAttr(self._attr_inn_yw, grp_output.rotateY) pymel.connectAttr(self._attr_inn_rl, grp_output.rotateZ) pymel.connectAttr(self._attr_inn_sx, grp_output.scaleX) pymel.connectAttr(self._attr_inn_sy, grp_output.scaleY) pymel.connectAttr(self._attr_inn_sz, grp_output.scaleZ) return grp_output.matrix
def build(self, *args, **kwargs): super(AdditiveFK, self).build(*args, **kwargs) nomenclature_anm = self.get_nomenclature_anm() # Ensure to create the finger ctrl in the good orientation if nomenclature_anm.side == self.rig.nomenclature.SIDE_L: normal_data = {constants.Axis.x: (1, 0, 0), constants.Axis.y: (0, 1, 0), constants.Axis.z: (0, 0, 1)} else: normal_data = {constants.Axis.x: (-1, 0, 0), constants.Axis.y: (0, -1, 0), constants.Axis.z: (0, 0, -1)} # TODO: Support multiple additive ctrls # TODO: Rename self.additive_ctrls = filter(None, self.additive_ctrls) if not self.additive_ctrls: ctrl_add = CtrlFkAdd() self.additive_ctrls.append(ctrl_add) #HACK - Temp since we don't support multiple ctrl for the moment ctrl_add = self.additive_ctrls[0] for i, ctrl in enumerate(self.additive_ctrls): name = nomenclature_anm.resolve("addFk{0:02d}".format(i)) ctrl.build(name=name, refs=self.chain.start, normal=normal_data[self.rig._up_axis]) ctrl.offset.setMatrix(self.chain.start.getMatrix(worldSpace=True)) ctrl.setParent(self.grp_anm) for i, ctrl in enumerate(self.ctrls): #HACK Add a new layer if this is the first ctrl to prevent Gimbal lock problems if i == 0: ctrl.offset = ctrl.append_layer("gimbal") attr_rotate_x = libRigging.create_utility_node('addDoubleLinear', input1=ctrl.offset.rotateX.get(), input2=ctrl_add.rotateX ).output attr_rotate_y = libRigging.create_utility_node('addDoubleLinear', input1=ctrl.offset.rotateY.get(), input2=ctrl_add.rotateY ).output attr_rotate_z = libRigging.create_utility_node('addDoubleLinear', input1=ctrl.offset.rotateZ.get(), input2=ctrl_add.rotateZ ).output pymel.connectAttr(attr_rotate_x, ctrl.offset.rotateX) pymel.connectAttr(attr_rotate_y, ctrl.offset.rotateY) pymel.connectAttr(attr_rotate_z, ctrl.offset.rotateZ) # Constraint the fk ctrls in position to the additive fk ctrls pymel.pointConstraint(ctrl_add, self.ctrls[0].offset)
def _connect_matrix_attr_to_transform(attr_tm, node): util_decompose = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_tm ) pymel.connectAttr(util_decompose.outputTranslate, node.translate) pymel.connectAttr(util_decompose.outputRotate, node.rotate) pymel.connectAttr(util_decompose.outputScale, node.scale)
def _connect_with_blend(attrs, attr_dst, attr_amount): """Quick function that create two attributes with a blend factor.""" attr_blended = libRigging.create_utility_node( 'blendTwoAttr', attributesBlender=attr_amount, input=attrs, ).output pymel.connectAttr(attr_blended, attr_dst)
def _get_follicle_relative_uv_attr(self, mult_u=1.0, mult_v=1.0): """ Resolve the relative parameterU and parameterV that will be sent to the follicles. :return: A tuple containing two pymel.Attribute: the relative parameterU and relative parameterV. """ # Apply custom multiplier attr_u = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_lr, input2X=self._attr_u_mult_inn).outputX attr_v = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_ud, input2X=self._attr_v_mult_inn).outputX return attr_u, attr_v
def _connect_with_blend(attr_src, attr_dst, attr_amount): """Quick function that create two attributes with a blend factor.""" attr_blended = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_src, input2X=attr_amount, ).outputX pymel.connectAttr(attr_blended, attr_dst)
def build_stack(self, stack, aim_target=None, **kwargs): nomenclature_rig = self.get_nomenclature_rig() # Build an aim node in-place for performance # This separated node allow the joints to be driven by the avars. aim_grp_name = nomenclature_rig.resolve('lookgrp') aim_grp = pymel.createNode('transform', name=aim_grp_name) aim_node_name = nomenclature_rig.resolve('looknode') aim_node = pymel.createNode('transform', name=aim_node_name) aim_node.setParent(aim_grp) aim_target_name = nomenclature_rig.resolve('target') aim_target = pymel.createNode('transform', name=aim_target_name) aim_target.setParent(aim_grp) self.target = aim_target # Build an upnode for the eyes. # I'm not a fan of upnodes but in this case it's better to guessing the joint orient. aim_upnode_name = nomenclature_rig.resolve('upnode') aim_upnode = pymel.createNode('transform', name=aim_upnode_name) # aim_upnode.setParent(self.grp_rig) pymel.parentConstraint(aim_grp, aim_upnode, maintainOffset=True) pymel.aimConstraint(aim_target, aim_node, maintainOffset=True, aimVector=(0.0, 0.0, 1.0), upVector=(0.0, 1.0, 0.0), worldUpObject=aim_upnode, worldUpType='object' ) # Position objects aim_grp.setParent(self._grp_offset) # todo: add begin , end property aim_grp.t.set(0,0,0) aim_grp.r.set(0,0,0) jnt_tm = self.jnt.getMatrix(worldSpace=True) jnt_pos = jnt_tm.translate aim_upnode_pos = pymel.datatypes.Point(0,1,0) + jnt_pos aim_upnode.setTranslation(aim_upnode_pos, space='world') aim_target_pos = pymel.datatypes.Point(0,0,1) + jnt_pos aim_target.setTranslation(aim_target_pos, space='world') pymel.parentConstraint(aim_node, stack, maintainOffset=True) # Convert the rotation to avars to additional values can be added. util_decomposeMatrix = libRigging.create_utility_node('decomposeMatrix', inputMatrix=aim_node.matrix) libRigging.connectAttr_withBlendWeighted(util_decomposeMatrix.outputTranslateX, self.attr_lr) libRigging.connectAttr_withBlendWeighted(util_decomposeMatrix.outputTranslateY, self.attr_ud) libRigging.connectAttr_withBlendWeighted(util_decomposeMatrix.outputTranslateZ, self.attr_fb) libRigging.connectAttr_withBlendWeighted(util_decomposeMatrix.outputRotateY, self.attr_yw) libRigging.connectAttr_withBlendWeighted(util_decomposeMatrix.outputRotateX, self.attr_pt) libRigging.connectAttr_withBlendWeighted(util_decomposeMatrix.outputRotateZ, self.attr_rl)
def _connect_matrix_to_trs(attr_inn, obj_out): # Connect internal_obj decomposeTM = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_inn ) pymel.connectAttr(decomposeTM.outputTranslate, obj_out.translate) pymel.connectAttr(decomposeTM.outputRotate, obj_out.rotate) pymel.connectAttr(decomposeTM.outputScale, obj_out.scale)
def _get_follicle_relative_uv_attr(self, mult_u=1.0, mult_v=1.0): """ Resolve the relative parameterU and parameterV that will be sent to the follicles. :return: A tuple containing two pymel.Attribute: the relative parameterU and relative parameterV. """ # Apply custom multiplier attr_u = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_lr, input2X=self._attr_u_mult_inn ).outputX attr_v = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_ud, input2X=self._attr_v_mult_inn ).outputX return attr_u, attr_v
def post_buid_module(self, module): super(RigSqueeze, self).post_buid_module(module) # # Connect all IK/FK attributes # TODO: Ensure all attributes are correctly transfered # if isinstance(module, rigLimb.Limb): # Inverse IK/FK state. # At Squeeze, 0 is IK and 1 is FK, strange. module.STATE_IK = 0.0 module.STATE_FK = 1.0 pymel.delete(module.ctrl_attrs) module.ctrl_attrs = None # Resolve name # TODO: Handle name conflict nomenclature_anm = module.get_nomenclature_anm(self) nomenclature_attr = self.nomenclature(tokens=[module.__class__.__name__], side=nomenclature_anm.side) attr_src_name = nomenclature_attr.resolve() attr_dst = module.grp_rig.attr(module.kAttrName_State) if not self.grp_anm.hasAttr(self.GROUP_NAME_IKFK, checkShape=False): libAttr.addAttr_separator(self.grp_anm, self.GROUP_NAME_IKFK) attr_src = None if not self.grp_anm.hasAttr(attr_src_name, checkShape=False): attr_src = libAttr.addAttr(self.grp_anm, longName=attr_src_name, at='short', k=True, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=0) else: attr_src = self.grp_anm.attr(attr_src_name) # Note that at Squeeze, 0 is for IK and 1 is for FK so we'll need to reverse it. attr_src_inv = libRigging.create_utility_node('reverse', inputX=attr_src).outputX pymel.connectAttr(attr_src_inv, attr_dst) # # Set ctrls colors # color_by_side = { self.nomenclature.SIDE_L: 13, # Red self.nomenclature.SIDE_R: 6 # Blue } epsilon = 0.1 if module.grp_anm: nomenclature_anm = module.get_nomenclature_anm(self) for ctrl in module.get_ctrls(recursive=True): nomenclature_ctrl = nomenclature_anm.rebuild(ctrl.name()) side = nomenclature_ctrl.side color = color_by_side.get(side, None) if color: ctrl.drawOverride.overrideEnabled.set(1) ctrl.drawOverride.overrideColor.set(color)
def build(self, rig, *args, **kwargs): super(AdditiveFK, self).build(rig, *args, **kwargs) nomenclature_anm = self.get_nomenclature_anm(rig) # TODO: Support multiple additive ctrls # TODO: Rename self.additive_ctrls = filter(None, self.additive_ctrls) if not self.additive_ctrls: ctrl_add = CtrlFkAdd() self.additive_ctrls.append(ctrl_add) #HACK - Temp since we don't support multiple ctrl for the moment ctrl_add = self.additive_ctrls[0] for i, ctrl in enumerate(self.additive_ctrls): name = nomenclature_anm.resolve("addFk{0:02d}".format(i)) ctrl.build(rig, name=name, refs=self.chain.start) ctrl.offset.setMatrix(self.chain.start.getMatrix(worldSpace=True)) ctrl.setParent(self.grp_anm) for i, ctrl in enumerate(self.ctrls): #HACK Add a new layer if this is the first ctrl to prevent Gimbal lock problems if i == 0: ctrl.offset = ctrl.add_layer("gimbal") attr_rotate_x = libRigging.create_utility_node('addDoubleLinear', input1=ctrl.offset.rotateX.get(), input2=ctrl_add.rotateX ).output attr_rotate_y = libRigging.create_utility_node('addDoubleLinear', input1=ctrl.offset.rotateY.get(), input2=ctrl_add.rotateY ).output attr_rotate_z = libRigging.create_utility_node('addDoubleLinear', input1=ctrl.offset.rotateZ.get(), input2=ctrl_add.rotateZ ).output pymel.connectAttr(attr_rotate_x, ctrl.offset.rotateX) pymel.connectAttr(attr_rotate_y, ctrl.offset.rotateY) pymel.connectAttr(attr_rotate_z, ctrl.offset.rotateZ) # Constraint the fk ctrls in position to the additive fk ctrls pymel.pointConstraint(ctrl_add, self.ctrls[0].offset)
def follicle_conditon_act(follicle, ctrl_master): follicle_condition_node = create_utility_node( "condition", name="follicle_condition", secondTerm=1, firstTerm=ctrl_master.springActivation ) # firstTerm=ctrl_master.springActivation follicle_condition_node.colorIfFalseR.set(0) follicle_condition_node.colorIfTrueR.set(2) follicle_condition_node.outColorR.connect( follicle.simulationMethod) # cannot connect via utility node.
def build(self, *args, **kwargs): super(AvarJaw, self).build(*args, **kwargs) # HACK: Hijack the jaw PT avar so the jaw don't go over 0. # TODO: Bulletproof attr_pt_out = next(iter(self.attr_pt.outputs(plugs=True, skipConversionNodes=True)), None) attr_pt_clamp = libRigging.create_utility_node('condition', operation=2, # Greater than firstTerm=self.attr_pt, colorIfTrueR=self.attr_pt, colorIfFalseR=0.0 ).outColorR pymel.connectAttr(attr_pt_clamp, attr_pt_out, force=True)
def _blend_inn_matrix_attribute(attr_tm, attr_blend_tx, attr_blend_ty, attr_blend_tz, attr_blend_rx, attr_blend_ry, attr_blend_rz, attr_blend_sx, attr_blend_sy, attr_blend_sz): # todo: replace with a matrixBlend node? u_decompose_a = libRigging.create_utility_node('decomposeMatrix', inputMatrix=attr_tm) attr_blend_t = libRigging.create_utility_node( 'multiplyDivide', input1X=u_decompose_a.outputTranslateX, input1Y=u_decompose_a.outputTranslateY, input1Z=u_decompose_a.outputTranslateZ, input2X=attr_blend_tx, input2Y=attr_blend_ty, input2Z=attr_blend_tz, ).output attr_blend_r = libRigging.create_utility_node( 'multiplyDivide', input1X=u_decompose_a.outputRotateX, input1Y=u_decompose_a.outputRotateY, input1Z=u_decompose_a.outputRotateZ, input2X=attr_blend_rx, input2Y=attr_blend_ry, input2Z=attr_blend_rz, ).output attr_blend_s = libRigging.create_utility_node( 'multiplyDivide', input1X=u_decompose_a.outputScaleX, input1Y=u_decompose_a.outputScaleY, input1Z=u_decompose_a.outputScaleZ, input2X=attr_blend_sx, input2Y=attr_blend_sy, input2Z=attr_blend_sz, ).output return libRigging.create_utility_node( 'composeMatrix', inputTranslate=attr_blend_t, inputRotate=attr_blend_r, inputScale=attr_blend_s, ).outputMatrix
def _connect_ctrl(self, ctrl, attr_ud=None, attr_lr=None, attr_fb=None, attr_yw=None, attr_pt=None, attr_rl=None): need_flip = self.need_flip_lr() if attr_ud: attr_inn_ud = ctrl.translateY libRigging.connectAttr_withBlendWeighted(attr_inn_ud, attr_ud) if attr_lr: attr_inn_lr = ctrl.translateX if need_flip: attr_inn_lr = libRigging.create_utility_node('multiplyDivide', input1X=attr_inn_lr, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_lr, attr_lr) if attr_fb: attr_inn_fb = ctrl.translateZ libRigging.connectAttr_withBlendWeighted(attr_inn_fb, attr_fb) if attr_yw: attr_inn_yw = ctrl.rotateY if need_flip: attr_inn_yw = libRigging.create_utility_node('multiplyDivide', input1X=attr_inn_yw, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_yw, attr_yw) if attr_pt: attr_inn_pt = ctrl.rotateX libRigging.connectAttr_withBlendWeighted(attr_inn_pt, attr_pt) if attr_rl: attr_inn_rl = ctrl.rotateZ if need_flip: attr_inn_rl = libRigging.create_utility_node('multiplyDivide', input1X=attr_inn_rl, input2X=-1).outputX libRigging.connectAttr_withBlendWeighted(attr_inn_rl, attr_rl)
def build(self, *args, **kwargs): super(AvarJaw, self).build(*args, **kwargs) # HACK: Hijack the jaw PT avar so the jaw don't go over 0. # TODO: Bulletproof attr_pt_out = next( iter(self.attr_pt.outputs(plugs=True, skipConversionNodes=True)), None) attr_pt_clamp = libRigging.create_utility_node( 'condition', operation=2, # Greater than firstTerm=self.attr_pt, colorIfTrueR=self.attr_pt, colorIfFalseR=0.0).outColorR pymel.connectAttr(attr_pt_clamp, attr_pt_out, force=True)
def create_spaceswitch(self, rig, module, parent, add_default=True, default_name=None, add_world=False, **kwargs): # TODO: Handle when parent is None? nomenclature = rig.nomenclature if parent is None: log.warning("Can't add space switch on {0}. No parent found!".format(self.node.__melobject__())) return # Resolve spaceswitch targets targets, labels = self.get_spaceswitch_targets(rig, module, parent, add_world=add_world) if not targets: log.warning("Can't add space switch on {0}. No targets found!".format(self.node.__melobject__())) return if default_name is None: default_name = 'Local' # Resolve the niceName of the targets for i in range(len(targets)): target = targets[i] label = labels[i] if label is None: name = nomenclature(target.name()) name.remove_extra_tokens() labels[i] = name.resolve() offset = 0 if add_default: offset += 1 labels.insert(0, default_name) layer_spaceSwitch = self.add_layer('spaceSwitch') parent_constraint = pymel.parentConstraint(targets, layer_spaceSwitch, maintainOffset=True, **kwargs) attr_space = libAttr.addAttr(self.node, 'space', at='enum', enumName=':'.join(labels), k=True) atts_weights = parent_constraint.getWeightAliasList() for i, att_weight in enumerate(atts_weights): index_to_match = i + offset att_enabled = libRigging.create_utility_node( #Equal 'condition', firstTerm=attr_space, secondTerm=index_to_match, colorIfTrueR=1, colorIfFalseR=0 ).outColorR pymel.connectAttr(att_enabled, att_weight)
def connect_ctrl(self, ctrl, **kwargs): attr_pt_inn = ctrl.translateY attr_yw_inn = ctrl.translateX # UD Low attr_pt_low = libRigging.create_utility_node('multiplyDivide', input1X=attr_pt_inn, input2X=-1).outputX ''' attr_pt_inn = libRigging.create_utility_node('condition', operation=4, # Less than firstTerm=attr_pt_inn, colorIfTrueR=attr_pt_low, colorIfFalseR=0.0 ).outColorR ''' libRigging.connectAttr_withLinearDrivenKeys( attr_pt_low, self.attr_pt, kv=[0.0, 0.0, 10.0] ) libRigging.connectAttr_withLinearDrivenKeys( attr_yw_inn, self.attr_yw, kv=[-5.0, 0.0, 5.0] )
def create(arg1, arg2): arg1, arg2 = OperatorDistance._handle_args(arg1, arg2) log.debug('[distance:create] {0} * {1}'.format(arg1, arg2)) # todo: check if we want to use inMatrix1 & inMatrix2 or point1 & point2 kwargs = {} if isinstance(arg1, pymel.datatypes.Matrix) or (isinstance(arg1, pymel.Attribute) or arg1.type() == 'matrix'): kwargs['inMatrix1'] = arg1 elif isinstance(arg1, pymel.nodetypes.Transform): kwargs['inMatrix1'] = arg1.worldMatrix else: kwargs['point1'] = arg1 if isinstance(arg2, pymel.datatypes.Matrix) or (isinstance(arg2, pymel.Attribute) or arg2.type() == 'matrix'): kwargs['inMatrix2'] = arg2 elif isinstance(arg2, pymel.nodetypes.Transform): kwargs['inMatrix2'] = arg2.worldMatrix else: kwargs['point2'] = arg2 return libRigging.create_utility_node('distanceBetween', **kwargs).distance
def adapt_to_orig_shape(source, shape_orig): """ :param source: source shape to transfer :param shape_orig: target to transfer to This is based out of Renaud's code on shape to orig when building and unbuilding with omtk to preserve shape info. """ def get_transformGeometry(target): return next((hist for hist in target.listHistory() if isinstance(hist, pymel.nodetypes.TransformGeometry)), None) # Resolve compensation matrix util_transform_geometry = get_transformGeometry(shape_orig) if not util_transform_geometry: shape_orig.warning( "Skipping {}. Cannot find transformGeometry.".format(shape_orig)) return attr_compensation_tm = next( iter(util_transform_geometry.transform.inputs(plugs=True)), None) if not attr_compensation_tm: shape_orig.warning( "Skipping {}. Cannot find compensation matrix.".format(shape_orig)) return tmp_transform_geometry = libRigging.create_utility_node( 'transformGeometry', inputGeometry=source.local, transform=attr_compensation_tm, invertTransform=True) # source.getParent().setParent(grp_offset) JG modification source should already be in place pymel.connectAttr(tmp_transform_geometry.outputGeometry, shape_orig.create, f=True) # Cleanup pymel.refresh(force=True) # but why do I have to refresh?! pymel.disconnectAttr(shape_orig.create) pymel.delete(tmp_transform_geometry)
def connect_ctrl(self, ctrl, **kwargs): attr_pt_inn = ctrl.translateY attr_yw_inn = ctrl.translateX # UD Low attr_pt_low = libRigging.create_utility_node('multiplyDivide', input1X=attr_pt_inn, input2X=-1).outputX ''' attr_pt_inn = libRigging.create_utility_node('condition', operation=4, # Less than firstTerm=attr_pt_inn, colorIfTrueR=attr_pt_low, colorIfFalseR=0.0 ).outColorR ''' libRigging.connectAttr_withLinearDrivenKeys(attr_pt_low, self.attr_pt, kv=[0.0, 0.0, 15.0]) libRigging.connectAttr_withLinearDrivenKeys(attr_yw_inn, self.attr_yw, kv=[-5.0, 0.0, 5.0])
def adapt_to_orig_shape(source, target): """ :param source: source shape to transfer :param target: target to transfer to This is based out of Renaud's code on shape to orig when building and unbuilding with omtk to preserve shape info. """ def get_transformGeometry(shape): return next((hist for hist in shape.listHistory() if isinstance(hist, pymel.nodetypes.TransformGeometry)), None) # Resolve orig shape shape_orig = get_orig_shape(target) # Resolve compensation matrix util_transform_geometry = get_transformGeometry(target) if not util_transform_geometry: target.warning("Skipping {}. Cannot find transformGeometry.".format(target)) return attr_compensation_tm = next(iter(util_transform_geometry.transform.inputs(plugs=True)), None) if not attr_compensation_tm: target.warning("Skipping {}. Cannot find compensation matrix.".format(target)) return tmp_transform_geometry = libRigging.create_utility_node( 'transformGeometry', inputGeometry=source.local, transform=attr_compensation_tm, invertTransform=True ) # source.getParent().setParent(grp_offset) JG modification source should already be in place pymel.connectAttr(tmp_transform_geometry.outputGeometry, shape_orig.create) # Cleanup pymel.refresh(force=True) # but why do I have to refresh^! pymel.disconnectAttr(shape_orig.create) pymel.delete(tmp_transform_geometry)
def _localize_matrice_value(world_source, holdMatrix, local_var=None, localize_type=None): """ :param world_source: world source is the sim locator. World_source must be a locator in chain before feeding it to local target. :param holdMatrix: bind_pose is is the kept parent of the ctrl data, child to the sim locator :param local_var: for "FK" previous sim locator in chain . for "IK" the master grp of the input curve rotation. :param localize_type: supported types = "FK","IK" :return: decomposeMatrix node """ if localize_type is "IK": attr_world = create_utility_node( 'multMatrix', matrixIn=(world_source.worldMatrix, local_var.worldInverseMatrix, holdMatrix.outMatrix)).matrixSum decompose_m = create_utility_node('decomposeMatrix', inputMatrix=attr_world) return decompose_m elif localize_type is "FK": attr_world_bindpose = create_utility_node( 'multMatrix', matrixIn=(holdMatrix.outMatrix, local_var.worldMatrix)).matrixSum attr_world_bindpose_inv = create_utility_node( 'inverseMatrix', inputMatrix=attr_world_bindpose).outputMatrix attr_local_tm = create_utility_node( 'multMatrix', matrixIn=(world_source.worldMatrix, attr_world_bindpose_inv)).matrixSum decompose_m = create_utility_node('decomposeMatrix', inputMatrix=attr_local_tm) return decompose_m
def _fix_ctrl_shape(self): """ When the rigger want to resize an InteractiveCtrl, he will modify the ctrl shape 'controlPoints' attributes. This can be problematic since the shape 'create' attribute is feed from a transformGeometry node to compensate the non-uniform scaling caused by the calibration. This will 'skew' the shape which we don't want. We always want to make sure that there's only data in the orig shape 'controlPoints' attributes. This method will create a temporary shape that will receive the 'local' attribute from the ctrl shape (which contain the deformation from the 'controlPoints' attribute). The 'local' attribute of that shape will then be fed back to the orig shape. Finally, all the original 'controlPoints' will be set to zero. """ if self.ctrl is None: return grp_offset = self.ctrl.offset def get_orig_shape(shape): return next((hist for hist in shape.listHistory() if isinstance(hist, pymel.nodetypes.NurbsCurve) and hist != shape and hist.intermediateObject.get()), None) def get_transformGeometry(shape): return next((hist for hist in shape.listHistory() if isinstance(hist, pymel.nodetypes.TransformGeometry)), None) for shape in self.ctrl.node.getShapes(noIntermediate=True): # Resolve orig shape shape_orig = get_orig_shape(shape) if not shape_orig: self.warning("Skipping {}. Cannot find orig shape.".format(shape)) continue # Resolve compensation matrix util_transform_geometry = get_transformGeometry(shape) if not util_transform_geometry: self.warning("Skipping {}. Cannot find transformGeometry.".format(shape)) continue attr_compensation_tm = next(iter(util_transform_geometry.transform.inputs(plugs=True)), None) if not attr_compensation_tm: self.warning("Skipping {}. Cannot find compensation matrix.".format(shape)) continue tmp_shape = pymel.createNode('nurbsCurve') tmp_shape.getParent().setParent(grp_offset) # Apply the inverted compensation matrix to access the desired orig_shape 'create' attr. tmp_transform_geometry = libRigging.create_utility_node( 'transformGeometry', inputGeometry=shape.local, transform=attr_compensation_tm, invertTransform=True ) attr_output_geometry = tmp_transform_geometry.outputGeometry pymel.connectAttr(attr_output_geometry, tmp_shape.create) pymel.disconnectAttr(tmp_shape.create) pymel.connectAttr(tmp_shape.local, shape_orig.create) # Remove any extraneous controlPoints coordinates. for attr_cp in shape.cp: attr_cp.set(0, 0, 0) for attr_cp in shape_orig.cp: attr_cp.set(0, 0, 0) # Cleanup pymel.disconnectAttr(shape_orig.create) pymel.delete(tmp_shape.getParent()) pymel.delete(tmp_transform_geometry)
def _get_follicle_relative_uv_attr(self, **kwargs): nomenclature_rig = self.get_nomenclature_rig() attr_u, attr_v = super(FaceLipsAvar, self)._get_follicle_relative_uv_attr(**kwargs) # # Create and connect Splitter Node # splitter = SplitterNode() splitter.build(nomenclature_rig, name=nomenclature_rig.resolve('splitter')) splitter.setParent(self.grp_rig) # Resolve the radius of the jaw influence. Used by the splitter. attr_jaw_radius = libRigging.create_utility_node( 'distanceBetween', name=nomenclature_rig.resolve('getJawRadius'), point1=self._grp_offset.translate, point2=self._target_jaw_bindpose.translate).distance # Resolve the jaw pitch. Used by the splitter. attr_jaw_pitch = libRigging.create_utility_node( 'decomposeMatrix', name=nomenclature_rig.resolve('getJawPitch'), inputMatrix=libRigging.create_utility_node( 'multMatrix', name=nomenclature_rig.resolve('extractJawPitch'), matrixIn=(self._target_jaw.worldMatrix, self._grp_parent.worldInverseMatrix )).matrixSum).outputRotateX # Connect the splitter inputs pymel.connectAttr(attr_u, splitter.attr_inn_surface_u) pymel.connectAttr(attr_v, splitter.attr_inn_surface_v) pymel.connectAttr(self._attr_inn_jaw_ratio_default, splitter.attr_inn_jaw_default_ratio) pymel.connectAttr(self._attr_length_v, splitter.attr_inn_surface_range_v) pymel.connectAttr(attr_jaw_radius, splitter.attr_inn_jaw_radius) pymel.connectAttr(attr_jaw_pitch, splitter.attr_inn_jaw_pt) pymel.connectAttr(self._attr_bypass_splitter, splitter.attr_inn_bypass) attr_u = splitter.attr_out_surface_u attr_v = splitter.attr_out_surface_v # Create constraint to controller the jaw reference attr_jaw_ratio = splitter.attr_out_jaw_ratio attr_jaw_ratio_inv = libRigging.create_utility_node( 'reverse', inputX=attr_jaw_ratio).outputX constraint_pr = pymel.parentConstraint(self._target_head, self._target_jaw, self._jaw_ref, maintainOffset=True) constraint_s = pymel.scaleConstraint(self._target_head, self._target_jaw, self._jaw_ref) weight_pr_head, weight_pr_jaw = constraint_pr.getWeightAliasList() weight_s_head, weight_s_jaw = constraint_s.getWeightAliasList() # Connect splitter outputs pymel.connectAttr(attr_jaw_ratio_inv, weight_pr_jaw) pymel.connectAttr(attr_jaw_ratio_inv, weight_s_jaw) pymel.connectAttr(attr_jaw_ratio, weight_pr_head) pymel.connectAttr(attr_jaw_ratio, weight_s_head) return attr_u, attr_v
def pre_build(self, create_grp_anm=True, create_display_layers=True, **kwargs): super(RigSqueeze, self).pre_build(create_grp_anm=create_grp_anm, create_master_grp=False, create_display_layers=create_display_layers, **kwargs) if create_grp_anm: grp_anim_size = CtrlMaster._get_recommended_radius(self) self.grp_anm_master = self.build_grp( CtrlMaster, self.grp_anm_master, self.nomenclature.root_anm_master_name, size=grp_anim_size ) if create_display_layers: pymel.editDisplayLayerMembers(self.layer_anm, self.grp_anm_master, noRecurse=True) # # Create specific group related to squeeze rig convention # all_geos = libPymel.ls_root_geos() # Build All_Grp self.grp_master = self.build_grp(classRig.RigGrp, self.grp_master, self.nomenclature.root_all_name) self.grp_model = self.build_grp(classRig.RigGrp, self.grp_model, self.nomenclature.root_model_name) self.grp_proxy = self.build_grp(classRig.RigGrp, self.grp_proxy, self.nomenclature.root_proxy_name) self.grp_fx = self.build_grp(classRig.RigGrp, self.grp_fx, self.nomenclature.root_fx_name) # Parent all groups in the main grp_master pymel.parent(self.grp_anm_master, self.grp_master) pymel.parent(self.grp_anm, self.grp_anm_master) # grp_anm is not a Node, but a Ctrl self.grp_rig.setParent(self.grp_master) self.grp_fx.setParent(self.grp_master) self.grp_model.setParent(self.grp_master) self.grp_proxy.setParent(self.grp_master) self.grp_geo.setParent(self.grp_master) ''' if self.grp_jnt.getParent() is None: self.grp_jnt.setParent(self.grp_master) ''' # Lock and hide all attributes we don't want the animator to play with libAttr.lock_hide_trs(self.grp_master) libAttr.lock_hide_trs(self.grp_rig) libAttr.lock_hide_trs(self.grp_fx) libAttr.lock_hide_trs(self.grp_model) libAttr.lock_hide_trs(self.grp_proxy) libAttr.lock_hide_trs(self.grp_geo) libAttr.hide_scale(self.grp_anm) # Hide some group # self.grp_jnt.visibility.set(False) self.grp_rig.visibility.set(False) self.grp_fx.visibility.set(False) self.grp_model.visibility.set(False) # # Add root ctrl attributes specific to squeeze while preserving existing connections. # if not self.grp_anm.hasAttr(self.GROUP_NAME_DISPLAY, checkShape=False): libAttr.addAttr_separator(self.grp_anm, self.GROUP_NAME_DISPLAY) attr_display_mesh_output_attrs = {self.grp_geo.visibility} attr_display_proxy_output_attrs = {self.grp_proxy.visibility} # attr_display_ctrl_output_attrs = set( # [children.visibility for children in self.grp_anm.getChildren(type='transform')] # ) # In the past, the displayMesh attribute was a boolean and the displayProxy was also a boolean. # Now we use an enum. This mean that we need to remap. if self.grp_anm.hasAttr(self.ATTR_NAME_DISPLAY_MESH): attr_display_mesh = self.grp_anm.attr(self.ATTR_NAME_DISPLAY_MESH) if attr_display_mesh.type() == 'short': for attr_dst in attr_display_mesh.outputs(plugs=True): attr_display_mesh_output_attrs.add(attr_dst) pymel.disconnectAttr(attr_display_mesh, attr_dst) if self.grp_anm.hasAttr(self.ATTR_NAME_DISPLAY_PROXY): attr_display_proxy = self.grp_anm.attr(self.ATTR_NAME_DISPLAY_PROXY) for attr_dst in attr_display_proxy.outputs(plugs=True): attr_display_proxy_output_attrs.add(attr_dst) pymel.disconnectAttr(attr_display_proxy, attr_dst) attr_display_proxy.delete() # Create DisplayMesh attribute attr_display_mesh = self._init_attr_display_mesh() # Create DisplayCtrl attribute attr_display_ctrl = self._init_attr_display_ctrl() # Connect DisplayMesh attribute for attr_dst in attr_display_mesh_output_attrs: if not libAttr.is_connected_to(attr_display_mesh, attr_dst, max_depth=3): self.debug("Connecting {} to {}".format(attr_display_mesh, attr_dst)) attr_proxy_display_inn = libRigging.create_utility_node( 'condition', firstTerm=attr_display_mesh, secondTerm=0, colorIfTrueR=True, colorIfFalseR=False ).outColorR pymel.connectAttr(attr_proxy_display_inn, attr_dst, force=True) for attr_dst in attr_display_proxy_output_attrs: if not libAttr.is_connected_to(attr_display_mesh, attr_dst, max_depth=3): self.debug("Connecting {} to {}".format(attr_display_mesh, attr_dst)) # attr_proxy_display_inn = libRigging.create_utility_node( # 'condition', # firstTerm=attr_display_mesh, # secondTerm=0, # colorIfTrueR=True, # colorIfFalseR=False # ).outColorR pymel.connectAttr(attr_display_mesh, attr_dst, force=True)
def _make_dynamic_setup(curve, ctrl_master, setup_name="test", rig_high_point=None, nucleus=None, hairSystem=None): """creates the default setup for maya dynamics. It affects as little as it can the nucleus because it is re used for all springs instead it clamps the value directly in the hairSystem node. It also make the connection to shut the dynamic without completly shutting down the follicle. static vs off in the hairSystem node""" def follicle_conditon_act(follicle, ctrl_master): follicle_condition_node = create_utility_node( "condition", name="follicle_condition", secondTerm=1, firstTerm=ctrl_master.springActivation ) # firstTerm=ctrl_master.springActivation follicle_condition_node.colorIfFalseR.set(0) follicle_condition_node.colorIfTrueR.set(2) follicle_condition_node.outColorR.connect( follicle.simulationMethod) # cannot connect via utility node. time = pm.PyNode("time1") if nucleus == None: nucleus = create_utility_node( "nucleus", name="{ctrl_name}_spring_nucleus".format( ctrl_name=ctrl_master.name().replace("_Ctrl", "")), currentTime=time.outTime, startFrame=ctrl_master.startFrame, enable=ctrl_master.springActivation, subSteps=ctrl_master.springSampling, timeScale=ctrl_master.timeScale) else: if not isinstance(nucleus, pm.nodetypes.Nucleus): raise Exception("{} isn\'t a Nucleus type pymel node ".format( str(nucleus))) follicle = create_utility_node( "follicle", degree=1, startDirection=1, restPose=1, startPosition=curve.local, startPositionMatrix=curve.getParent().worldMatrix[0]) follicle_p = follicle.getParent() pm.rename(follicle_p, "{}_follicle".format(setup_name)) follicle_p.setParent(rig_high_point) pm.makeIdentity(follicle_p, t=True, r=True, s=True) pm.parent(curve.getParent(), follicle_p) dynamic_curve = create_utility_node("nurbsCurve", create=follicle.outCurve) pm.rename(dynamic_curve.getParent(), "{}_DynamicCurve".format(setup_name)) if hairSystem == None: # free index based on the fact that nothing has been disconnected in the outputObjects in a bad way.(no match) x = _check_free_index(nucleus.outputObjects) hairSystem = create_utility_node("hairSystem", active=True, collide=False, nextState=nucleus.outputObjects[x], startFrame=ctrl_master.startFrame, currentTime=time.outTime, motionDrag=ctrl_master.motionDrag) hairSystem.startState.connect(nucleus.inputActiveStart[x]) hairSystem.currentState.connect(nucleus.inputActive[x]) pm.rename(hairSystem.getParent(), "{}_nHair".format(setup_name)) follicle.outHair.connect(hairSystem.inputHair[0]) hairSystem.outputHair[0].connect(follicle.currentPosition) # the switch conditions to turn off the whole system down. nHair_condition_node = create_utility_node( "condition", name="nHair_condition", secondTerm=1, firstTerm=ctrl_master.springActivation ) # firstTerm=ctrl_master.springActivation nHair_condition_node.colorIfFalseR.set(1) nHair_condition_node.colorIfTrueR.set(3) nHair_condition_node.outColorR.connect( hairSystem.simulationMethod) # cannot connect via utility node. follicle_conditon_act(follicle, ctrl_master) else: if not isinstance(hairSystem, pm.nodetypes.HairSystem): raise Exception("{} isn\'t a HairSystem type pymel node ".format( str(hairSystem))) x = _check_free_index(hairSystem.inputHair) follicle.outHair.connect(hairSystem.inputHair[x]) hairSystem.outputHair[x].connect(follicle.currentPosition) follicle_conditon_act(follicle, ctrl_master) return dynamic_curve # this curve should be a functionnal dynamic curve
def create(*args, **kwargs): return libRigging.create_utility_node('condition', operation=5, colorIfTrue=1.0, colorIfFalse=0.0).outColorR
def create_stacks(self): """ Create the route to compute the output transform for the avar. This is done using node 'stacks' which allow multiple contribution from being added while still be 'clear' for the rigger which layer is providing which input. By keeping stack seperated, we are able to keep them in isolation and use the 'worldMatrix' of their leaf to easily compute the total contribution. This would result in the following matrix multiplications: 1) 'offset group' We start with the bind transform. The resulting matrix is in local (also known as pre-deform) space. 3) 'avar stack' This compute the desired movement from the avar values. Being a stack, it can be modified after creation if needed by adding layers to it. The resulting matrix is still in local space. 4) 'post avar' stack' This add any contribution needed after the avar. Being a stack, it can be modified after creation if needed by adding layers to it. Ex: Used for having the 'all' macro avar influence other avars. The resulting matrix is still in local space. 5) 'parent' group This matrix is identity at bind pose and will add the movement from the parent of the avar. The resulting matrix is in world space. The final transform is computed by multiplying all these stacks togheter: - offset.matrix - pre-avar.worldMatrix - avar.worldMatrix. - post-avar.worldMatrix - parent.matrix """ nomenclature_rig = self.get_nomenclature_rig() # Build post-avar stack # This is a list of matrix multiplication that will be executed AFTER feeding the avar. self._stack_post = classNode.Node() self._stack_post.build(name=nomenclature_rig.resolve('postAvar')) post_stack_root = pymel.createNode( 'transform', name=nomenclature_rig.resolve('postAvarRoot'), parent=self.grp_rig ) # layer_stack_input = self._stack_post.prepend_layer(name='input') self._stack_post.setParent(post_stack_root) libRigging.connect_matrix_to_node( self.grp_offset.matrix, post_stack_root, name=nomenclature_rig.resolve('something') ) attr_avar_model_tm = _blend_inn_matrix_attribute( self.model_infl._attr_out_tm, self.affect_tx, self.affect_ty, self.affect_tz, self.affect_rx, self.affect_ry, self.affect_rz, self.affect_sx, self.affect_sy, self.affect_sz, ) # Take the result of the stack and add it on top of the bind-pose and parent group. self._attr_get_stack_local_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=( # self._stack_pre.worldMatrix, # self._stack.worldMatrix, # self.model_infl._attr_out_tm, attr_avar_model_tm, self._stack_post.worldMatrix, # self._grp_offset.matrix, self._grp_parent.matrix, ) ).matrixSum util_get_stack_local_tm = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=self._attr_get_stack_local_tm ) pymel.connectAttr(util_get_stack_local_tm.outputTranslate, self._grp_output.translate) pymel.connectAttr(util_get_stack_local_tm.outputRotate, self._grp_output.rotate) pymel.connectAttr(util_get_stack_local_tm.outputScale, self._grp_output.scale)
def _blend_matrix_attribute(attr_tm_a, attr_tm_b, attr_blend_tx, attr_blend_ty, attr_blend_tz, attr_blend_rx, attr_blend_ry, attr_blend_rz, attr_blend_sx, attr_blend_sy, attr_blend_sz): # todo: replace with a matrixBlend node? u_decompose_a = libRigging.create_utility_node('decomposeMatrix', inputMatrix=attr_tm_a) u_decompose_b = libRigging.create_utility_node('decomposeMatrix', inputMatrix=attr_tm_b) u_compose_tm = libRigging.create_utility_node('composeMatrix') attr_blend_tx = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputTranslateX, u_decompose_b.outputTranslateX], attributesBlender=attr_blend_tx).output attr_blend_ty = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputTranslateY, u_decompose_b.outputTranslateY], attributesBlender=attr_blend_ty).output attr_blend_tz = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputTranslateZ, u_decompose_b.outputTranslateZ], attributesBlender=attr_blend_tz).output attr_blend_rx = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputRotateX, u_decompose_b.outputRotateX], attributesBlender=attr_blend_rx).output attr_blend_ry = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputRotateY, u_decompose_b.outputRotateY], attributesBlender=attr_blend_ry).output attr_blend_rz = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputRotateZ, u_decompose_b.outputRotateZ], attributesBlender=attr_blend_rz).output attr_blend_sx = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputScaleX, u_decompose_b.outputScaleX], attributesBlender=attr_blend_sx).output attr_blend_sy = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputScaleY, u_decompose_b.outputScaleY], attributesBlender=attr_blend_sy).output attr_blend_sz = libRigging.create_utility_node('blendTwoAttr', input=[u_decompose_a.outputScaleZ, u_decompose_b.outputScaleZ], attributesBlender=attr_blend_sz).output pymel.connectAttr(attr_blend_tx, u_compose_tm.inputTranslateX) pymel.connectAttr(attr_blend_ty, u_compose_tm.inputTranslateY) pymel.connectAttr(attr_blend_tz, u_compose_tm.inputTranslateZ) pymel.connectAttr(attr_blend_rx, u_compose_tm.inputRotateX) pymel.connectAttr(attr_blend_ry, u_compose_tm.inputRotateY) pymel.connectAttr(attr_blend_rz, u_compose_tm.inputRotateZ) pymel.connectAttr(attr_blend_sx, u_compose_tm.inputScaleX) pymel.connectAttr(attr_blend_sy, u_compose_tm.inputScaleY) pymel.connectAttr(attr_blend_sz, u_compose_tm.inputScaleZ) return u_compose_tm.outputMatrix
def build_stack(self, stack, aim_target=None, **kwargs): nomenclature_rig = self.get_nomenclature_rig() # Build an aim node in-place for performance # This separated node allow the joints to be driven by the avars. aim_grp_name = nomenclature_rig.resolve('lookgrp') aim_grp = pymel.createNode('transform', name=aim_grp_name) aim_node_name = nomenclature_rig.resolve('looknode') aim_node = pymel.createNode('transform', name=aim_node_name) aim_node.setParent(aim_grp) aim_target_name = nomenclature_rig.resolve('target') aim_target = pymel.createNode('transform', name=aim_target_name) aim_target.setParent(aim_grp) self.target = aim_target # Build an upnode for the eyes. # I'm not a fan of upnodes but in this case it's better to guessing the joint orient. aim_upnode_name = nomenclature_rig.resolve('upnode') aim_upnode = pymel.createNode('transform', name=aim_upnode_name) # aim_upnode.setParent(self.grp_rig) pymel.parentConstraint(aim_grp, aim_upnode, maintainOffset=True) pymel.aimConstraint(aim_target, aim_node, maintainOffset=True, aimVector=(0.0, 0.0, 1.0), upVector=(0.0, 1.0, 0.0), worldUpObject=aim_upnode, worldUpType='object') # Position objects aim_grp.setParent(self._grp_offset) # todo: add begin , end property aim_grp.t.set(0, 0, 0) aim_grp.r.set(0, 0, 0) jnt_tm = self.jnt.getMatrix(worldSpace=True) jnt_pos = jnt_tm.translate aim_upnode_pos = pymel.datatypes.Point(0, 1, 0) + jnt_pos aim_upnode.setTranslation(aim_upnode_pos, space='world') aim_target_pos = pymel.datatypes.Point(0, 0, 1) + jnt_pos aim_target.setTranslation(aim_target_pos, space='world') pymel.parentConstraint(aim_node, stack, maintainOffset=True) # Convert the rotation to avars to additional values can be added. util_decomposeMatrix = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=aim_node.matrix) libRigging.connectAttr_withBlendWeighted( util_decomposeMatrix.outputTranslateX, self.attr_lr) libRigging.connectAttr_withBlendWeighted( util_decomposeMatrix.outputTranslateY, self.attr_ud) libRigging.connectAttr_withBlendWeighted( util_decomposeMatrix.outputTranslateZ, self.attr_fb) libRigging.connectAttr_withBlendWeighted( util_decomposeMatrix.outputRotateY, self.attr_yw) libRigging.connectAttr_withBlendWeighted( util_decomposeMatrix.outputRotateX, self.attr_pt) libRigging.connectAttr_withBlendWeighted( util_decomposeMatrix.outputRotateZ, self.attr_rl)
def build(self, avar, ref=None, ref_tm=None, grp_rig=None, obj_mesh=None, u_coord=None, v_coord=None, flip_lr=False, follow_mesh=True, ctrl_tm=None, ctrl_size=1.0, parent_pos=None, parent_rot=None, parent_scl=None, constraint=False, cancel_t=True, cancel_r=True, **kwargs): super(ModelInteractiveCtrl, self).build(avar, ctrl_size=ctrl_size, **kwargs) nomenclature_rig = self.get_nomenclature_rig() # # Resolve necessary informations # # Resolve which object will the InteractiveCtrl track. # If we don't want to follow a particular geometry, we'll use the end of the stack. # Otherwise the influence will be used (to also resolve the geometry). # todo: it could be better to resolve the geometry ourself if ref is None: ref = self.jnt # Resolve ctrl matrix # It can differ from the influence to prevent the controller to appear in the geometry. if ctrl_tm is None: ctrl_tm = self.get_default_tm_ctrl() if ctrl_tm is None and ref_tm: ctrl_tm = ref_tm if ctrl_tm is None and self.jnt: ctrl_tm = self.jnt.getMatrix(worldSpace=True) if ctrl_tm is None: raise Exception("Cannot resolve ctrl transformation matrix!") pos_ref = self.project_pos_on_face(ctrl_tm.translate, geos=self.get_meshes()) # Resolve u and v coordinates # todo: check if we really want to resolve the u and v ourself since it's now connected. if obj_mesh is None: # We'll scan all available geometries and use the one with the shortest distance. meshes = libHistory.get_affected_shapes(ref) meshes = list(set(meshes) & set(self.rig.get_shapes())) if not meshes: meshes = set(self.rig.get_shapes()) obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes(meshes, pos_ref) if obj_mesh is None and follow_mesh: raise Exception("Can't find mesh affected by {0}. ".format(self.jnt)) else: _, out_u, out_v = libRigging.get_closest_point_on_shape(obj_mesh, pos_ref) # Resolve u and v coordinates if necesary. if u_coord is None: u_coord = out_u if v_coord is None: v_coord = out_v if self.jnt: self.debug('Creating doritos on {0} using {1} as reference'.format(obj_mesh, self.jnt)) else: self.debug('Creating doritos on {0}'.format(obj_mesh)) # Hack: Since there's scaling on the ctrl so the left and right side ctrl channels matches, we need to flip the ctrl shapes. if flip_lr: self.ctrl.scaleX.set(-1) libPymel.makeIdentity_safe(self.ctrl, rotate=True, scale=True, apply=True) # # Create the follicle setup # # Initialize external stack # Normally this would be hidden from animators. stack_name = nomenclature_rig.resolve('doritosStack') self._stack = Node(self) self._stack.build(name=stack_name) self._stack.setTranslation(pos_ref) # Create the layer_fol that will follow the geometry layer_fol_name = nomenclature_rig.resolve('doritosFol') layer_fol = self._stack.append_layer() layer_fol.rename(layer_fol_name) layer_fol.setParent(self.grp_rig) # # Constraint grp_rig # # Constraint position # TODO: Validate that we don't need to inverse the rotation separately. if parent_pos is None: fol_mesh = None if follow_mesh: fol_name = nomenclature_rig.resolve('follicle') fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord) fol_mesh = fol_shape.getParent() self.follicle = fol_mesh fol_mesh.rename(fol_name) fol_mesh.setParent(self.grp_rig) parent_pos = fol_mesh elif ref: parent_pos = ref if parent_pos: pymel.parentConstraint(parent_pos, layer_fol, maintainOffset=True, skipRotate=['x', 'y', 'z']) # Constraint rotation # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since # any deformation can affect the normal of the faces. if parent_rot: pymel.orientConstraint(parent_rot, layer_fol, maintainOffset=True) # # Constraint a specic controller to the avar doritos stack. # Call this method after connecting the ctrl to the necessary avars. # The sensibility of the doritos will be automatically computed in this step if necessary. # # Create inverted attributes for sensibility util_sensitivity_inv = libRigging.create_utility_node('multiplyDivide', operation=2, input1X=1.0, input1Y=1.0, input1Z=1.0, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz ) attr_sensibility_lr_inv = util_sensitivity_inv.outputX attr_sensibility_ud_inv = util_sensitivity_inv.outputY attr_sensibility_fb_inv = util_sensitivity_inv.outputZ # # Inverse translation # if cancel_t: attr_ctrl_inv_t = libRigging.create_utility_node( 'multiplyDivide', input1=self.ctrl.node.t, input2=[-1, -1, -1] ).output attr_ctrl_inv_t = libRigging.create_utility_node( 'multiplyDivide', input1=attr_ctrl_inv_t, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz ).output layer_inv_t = self._stack.append_layer(name='inverseT') if flip_lr: attr_doritos_tx = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_ctrl_inv_t.outputX, input2X=-1 ).outputX else: attr_doritos_tx = attr_ctrl_inv_t.outputX attr_doritos_ty = attr_ctrl_inv_t.outputY attr_doritos_tz = attr_ctrl_inv_t.outputZ pymel.connectAttr(attr_doritos_tx, layer_inv_t.tx) pymel.connectAttr(attr_doritos_ty, layer_inv_t.ty) pymel.connectAttr(attr_doritos_tz, layer_inv_t.tz) # # Inverse rotation # Add an inverse node that will counter animate the position of the ctrl. # TODO: Rename # if cancel_r: layer_inv_r = self._stack.append_layer(name='inverseR') # layer_doritos = pymel.createNode('transform', name=layer_doritos_name) # layer_doritos.setParent(self._stack.node) # Create inverse attributes for the ctrl attr_ctrl_inv_r = libRigging.create_utility_node( 'multiplyDivide', input1=self.ctrl.node.r, input2=[-1, -1, -1] ).output pymel.connectAttr(attr_ctrl_inv_r, layer_inv_r.r) # # Apply scaling on the ctrl parent. # This is were the 'black magic' happen. # if flip_lr: attr_ctrl_offset_sx_inn = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_sensitivity_tx, input2X=-1 ).outputX else: attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz # Connect any additionel scale source. if parent_scl: u = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getAbsoluteCtrlOffsetScale'), input1X=attr_ctrl_offset_sx_inn, input1Y=attr_ctrl_offset_sy_inn, input1Z=attr_ctrl_offset_sz_inn, input2X=parent_scl.scaleX, input2Y=parent_scl.scaleY, input2Z=parent_scl.scaleZ ) attr_ctrl_offset_sx_inn, attr_ctrl_offset_sy_inn, attr_ctrl_offset_sz_inn = u.outputX, u.outputY, u.outputZ pymel.connectAttr(attr_ctrl_offset_sx_inn, self.ctrl.offset.scaleX) pymel.connectAttr(attr_ctrl_offset_sy_inn, self.ctrl.offset.scaleY) pymel.connectAttr(attr_ctrl_offset_sz_inn, self.ctrl.offset.scaleZ) # Apply sensibility on the ctrl shape ctrl_shape = self.ctrl.node.getShape() tmp = pymel.duplicate(self.ctrl.node.getShape())[0] ctrl_shape_orig = tmp.getShape() ctrl_shape_orig.setParent(self.ctrl.node, relative=True, shape=True) ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name())) pymel.delete(tmp) ctrl_shape_orig.intermediateObject.set(True) for cp in ctrl_shape.cp: cp.set(0, 0, 0) # Counter-scale the shape attr_adjustement_sx_inn = attr_sensibility_lr_inv attr_adjustement_sy_inn = attr_sensibility_ud_inv attr_adjustement_sz_inn = attr_sensibility_fb_inv attr_adjustement_scale = libRigging.create_utility_node( 'composeMatrix', inputScaleX=attr_adjustement_sx_inn, inputScaleY=attr_adjustement_sy_inn, inputScaleZ=attr_adjustement_sz_inn ).outputMatrix attr_adjustement_rot = libRigging.create_utility_node( 'composeMatrix', inputRotateX=self.ctrl.node.rotateX, inputRotateY=self.ctrl.node.rotateY, inputRotateZ=self.ctrl.node.rotateZ ).outputMatrix attr_adjustement_rot_inv = libRigging.create_utility_node( 'inverseMatrix', inputMatrix=attr_adjustement_rot ).outputMatrix attr_adjustement_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=[ attr_adjustement_rot, attr_adjustement_scale, attr_adjustement_rot_inv ] ).matrixSum attr_transform_geometry = libRigging.create_utility_node( 'transformGeometry', transform=attr_adjustement_tm, inputGeometry=ctrl_shape_orig.local ).outputGeometry pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True) # # Constraint grp_anm # # Create an output object that will hold the world position of the ctrl offset. # We'll use direct connections to the animation controller to simplify the dag tree and support # non-uniform scaling (which is really hard to do using constraints). # Since the the model's ctrl will still be influenced by the root ctrl, we'll need to extract the offset # relative to the root ctrl. grp_output = pymel.createNode( 'transform', name=nomenclature_rig.resolve('output'), parent=self.grp_rig ) # Position stack_end = self._stack.get_stack_end() pymel.parentConstraint(stack_end, grp_output, maintainOffset=False, skipRotate=['x', 'y', 'z']) # Rotation if parent_rot is None: parent_rot = stack_end # parent_rot = layer_inv_r.getParent() pymel.orientConstraint(parent_rot, grp_output, maintainOffset=True) # Direct-connect the output group to the ctrl offset grp. # Since the ctrl is a child of the master ctrl, we'll need to take it's parent in consideration. util_get_ctrl_offset_local_trs = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=libRigging.create_utility_node( 'multMatrix', matrixIn=( grp_output.worldMatrix, self.rig.grp_anm.worldInverseMatrix ) ).matrixSum ) pymel.connectAttr(util_get_ctrl_offset_local_trs.outputTranslate, self.ctrl.offset.translate) pymel.connectAttr(util_get_ctrl_offset_local_trs.outputRotate, self.ctrl.offset.rotate) # Clean dag junk if grp_rig: self._stack.setParent(grp_rig) if fol_mesh: fol_mesh.setParent(grp_rig) if constraint and self.jnt: pymel.parentConstraint(self.ctrl.node, self.jnt, maintainOffset=True)
def build(self, nomenclature_rig, **kwargs): super(SplitterNode, self).build(**kwargs) # # Create inn and out attributes. # grp_splitter_inn = pymel.createNode( 'network', name=nomenclature_rig.resolve('udSplitterInn')) # The jaw opening amount in degree. self.attr_inn_jaw_pt = libAttr.addAttr(grp_splitter_inn, 'innJawOpen') # The relative uv coordinates normally sent to the follicles. # Note that this value is expected to change at the output of the SplitterNode (see outSurfaceU and outSurfaceV) self.attr_inn_surface_u = libAttr.addAttr(grp_splitter_inn, 'innSurfaceU') self.attr_inn_surface_v = libAttr.addAttr(grp_splitter_inn, 'innSurfaceV') # Use this switch to disable completely the splitter. self.attr_inn_bypass = libAttr.addAttr(grp_splitter_inn, 'innBypassAmount') # The arc length in world space of the surface controlling the follicles. self.attr_inn_surface_range_v = libAttr.addAttr( grp_splitter_inn, 'innSurfaceRangeV' ) # How many degree does take the jaw to create 1 unit of surface deformation? (ex: 20) # How much inn percent is the lips following the jaw by default. # Note that this value is expected to change at the output of the SplitterNode (see attr_out_jaw_ratio) self.attr_inn_jaw_default_ratio = libAttr.addAttr( grp_splitter_inn, 'jawDefaultRatio') # The radius of the influence circle normally resolved by using the distance between the jaw and the avar as radius. self.attr_inn_jaw_radius = libAttr.addAttr(grp_splitter_inn, 'jawRadius') grp_splitter_out = pymel.createNode( 'network', name=nomenclature_rig.resolve('udSplitterOut')) self.attr_out_surface_u = libAttr.addAttr(grp_splitter_out, 'outSurfaceU') self.attr_out_surface_v = libAttr.addAttr(grp_splitter_out, 'outSurfaceV') self.attr_out_jaw_ratio = libAttr.addAttr( grp_splitter_out, 'outJawRatio' ) # How much percent this influence follow the jaw after cancellation. # # Connect inn and out network nodes so they can easily be found from the SplitterNode. # attr_inn = libAttr.addAttr(grp_splitter_inn, longName='inn', attributeType='message') attr_out = libAttr.addAttr(grp_splitter_out, longName='out', attributeType='message') pymel.connectAttr(self.node.message, attr_inn) pymel.connectAttr(self.node.message, attr_out) # # Create node networks # Step 1: Get the jaw displacement in uv space (parameterV only). # attr_jaw_circumference = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getJawCircumference'), input1X=self.attr_inn_jaw_radius, input2X=(math.pi * 2.0)).outputX attr_jaw_open_circle_ratio = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getJawOpenCircleRatio'), operation=2, # divide input1X=self.attr_inn_jaw_pt, input2X=360.0).outputX attr_jaw_active_circumference = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getJawActiveCircumference'), input1X=attr_jaw_circumference, input2X=attr_jaw_open_circle_ratio).outputX # We need this adjustment since we cheat the influence of the avar with the plane uvs. # see AvarFollicle._get_follicle_relative_uv_attr for more information. # attr_jaw_radius_demi = libRigging.create_utility_node( # 'multiplyDivide', # name=nomenclature_rig.resolve('getJawRangeVRange'), # input1X=self.attr_inn_surface_range_v, # input2X=2.0 # ).outputX attr_jaw_v_range = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getActiveJawRangeInSurfaceSpace'), operation=2, # divide input1X=attr_jaw_active_circumference, input2X=self.attr_inn_surface_range_v).outputX # # Step 2: Resolve attr_out_jaw_ratio # # Convert attr_jaw_default_ratio in uv space. attr_jaw_default_ratio_v = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getJawDefaultRatioUvSpace'), input1X=self.attr_inn_jaw_default_ratio, input2X=attr_jaw_v_range).outputX attr_jaw_uv_pos = libRigging.create_utility_node( 'plusMinusAverage', name=nomenclature_rig.resolve('getCurrentJawUvPos'), operation=2, # substraction input1D=(attr_jaw_default_ratio_v, self.attr_inn_surface_v)).output1D attr_jaw_ratio_out = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getJawRatioOut'), operation=2, # division input1X=attr_jaw_uv_pos, input2X=attr_jaw_v_range).outputX attr_jaw_ratio_out_limited = libRigging.create_utility_node( 'clamp', name=nomenclature_rig.resolve('getLimitedJawRatioOut'), inputR=attr_jaw_ratio_out, minR=0.0, maxR=1.0).outputR # Prevent division by zero attr_jaw_ratio_out_limited_safe = libRigging.create_utility_node( 'condition', name=nomenclature_rig.resolve('getSafeJawRatioOut'), operation=1, # not equal firstTerm=self.attr_inn_jaw_pt, secondTerm=0, colorIfTrueR=attr_jaw_ratio_out_limited, colorIfFalseR=self.attr_inn_jaw_default_ratio).outColorR # # Step 3: Resolve attr_out_surface_u & attr_out_surface_v # attr_inn_jaw_default_ratio_inv = libRigging.create_utility_node( 'reverse', name=nomenclature_rig.resolve('getJawDefaultRatioInv'), inputX=self.attr_inn_jaw_default_ratio).outputX util_jaw_uv_default_ratio = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getJawDefaultRatioUvSpace'), input1X=self.attr_inn_jaw_default_ratio, input1Y=attr_inn_jaw_default_ratio_inv, input2X=attr_jaw_v_range, input2Y=attr_jaw_v_range) attr_jaw_uv_default_ratio = util_jaw_uv_default_ratio.outputX attr_jaw_uv_default_ratio_inv = util_jaw_uv_default_ratio.outputY attr_jaw_uv_limit_max = libRigging.create_utility_node( 'plusMinusAverage', name=nomenclature_rig.resolve('getJawSurfaceLimitMax'), operation=2, # substract input1D=(attr_jaw_v_range, attr_jaw_uv_default_ratio_inv)).output1D attr_jaw_uv_limit_min = libRigging.create_utility_node( 'plusMinusAverage', name=nomenclature_rig.resolve('getJawSurfaceLimitMin'), operation=2, # substract input1D=(attr_jaw_uv_default_ratio, attr_jaw_v_range)).output1D attr_jaw_cancel_range = libRigging.create_utility_node( 'clamp', name=nomenclature_rig.resolve('getJawCancelRange'), inputR=self.attr_inn_surface_v, minR=attr_jaw_uv_limit_min, maxR=attr_jaw_uv_limit_max).outputR attr_out_surface_v_cancelled = libRigging.create_utility_node( 'plusMinusAverage', name=nomenclature_rig.resolve('getCanceledUv'), operation=2, # substraction input1D=(self.attr_inn_surface_v, attr_jaw_cancel_range)).output1D # # Connect output attributes # attr_inn_bypass_inv = libRigging.create_utility_node( 'reverse', name=nomenclature_rig.resolve('getBypassInv'), inputX=self.attr_inn_bypass).outputX # Connect output jaw_ratio attr_output_jaw_ratio = libRigging.create_utility_node( 'blendWeighted', input=(attr_jaw_ratio_out_limited_safe, self.attr_inn_jaw_default_ratio), weight=(attr_inn_bypass_inv, self.attr_inn_bypass)).output pymel.connectAttr(attr_output_jaw_ratio, self.attr_out_jaw_ratio) # Connect output surface u pymel.connectAttr(self.attr_inn_surface_u, self.attr_out_surface_u) # Connect output surface_v attr_output_surface_v = libRigging.create_utility_node( 'blendWeighted', input=(attr_out_surface_v_cancelled, self.attr_inn_surface_v), weight=(attr_inn_bypass_inv, self.attr_inn_bypass)).output pymel.connectAttr(attr_output_surface_v, self.attr_out_surface_v)
def build(self, *args, **kwargs): super(Limb, self).build(*args, **kwargs) nomenclature_anm = self.get_nomenclature_anm() nomenclature_rig = self.get_nomenclature_rig() # Create IK system if not isinstance(self.sysIK, self._CLASS_SYS_IK): self.sysIK = self._CLASS_SYS_IK(self.chain_jnt, rig=self.rig) self.sysIK.name = '{0}_Ik'.format(self.name) # Hack self.sysIK.build(constraint=False, **kwargs) # Create FK system if not isinstance(self.sysFK, self._CLASS_SYS_FK): self.sysFK = self._CLASS_SYS_FK(self.chain_jnt, rig=self.rig) self.sysFK.name = '{0}_Fk'.format(self.name) # Hack self.sysFK.build(constraint=False, **kwargs) # Create twistbone system if needed if self.create_twist: num_twist_sys = self.sysIK.iCtrlIndex # If the IK system is a quad, we need to have two twist system for i in range(0, num_twist_sys): cur_sys_twist = self.sys_twist[i] if i < len(self.sys_twist) else None if not isinstance(cur_sys_twist, self._CLASS_SYS_TWIST): cur_sys_twist = self._CLASS_SYS_TWIST(self.chain_jnt[i:(i+2)], rig=self.rig) self.sys_twist.append(cur_sys_twist) # Hack twist_sys_name = self.chain_jnt[i].name().replace('_' + nomenclature_rig.type_jnt, "") cur_sys_twist.name = '{0}'.format(twist_sys_name) cur_sys_twist.build(num_twist=3, create_bend=True, **kwargs) # Lock X and Y axis on the elbow/knee ctrl if self.rig._up_axis == constants.Axis.y: libAttr.lock_hide_rotation(self.sysFK.ctrls[1], z=False) elif self.rig._up_axis == constants.Axis.z: libAttr.lock_hide_rotation(self.sysFK.ctrls[1], y=False) # Store the offset between the ik ctrl and it's joint equivalent. # Useful when they don't match for example on a leg setup. self.offset_ctrl_ik = self.sysIK.ctrl_ik.getMatrix(worldSpace=True) * self.chain_jnt[self.iCtrlIndex].getMatrix(worldSpace=True).inverse() # Add attributes to the attribute holder. # Add ikFk state attribute on the grp_rig. # This is currently controlled by self.ctrl_attrs. pymel.addAttr(self.grp_rig, longName=self.kAttrName_State, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=1, k=True) attr_ik_weight = self.grp_rig.attr(self.kAttrName_State) attr_fk_weight = libRigging.create_utility_node('reverse', inputX=attr_ik_weight).outputX # Create attribute holder (this is where the IK/FK attribute will be stored) # Note that this is production specific and should be defined in a sub-class implementation. jnt_hand = self.chain_jnt[self.sysIK.iCtrlIndex] ctrl_attrs_name = nomenclature_anm.resolve('atts') if not isinstance(self.ctrl_attrs, self._CLASS_CTRL_ATTR): self.ctrl_attrs = self._CLASS_CTRL_ATTR() self.ctrl_attrs.build(name=ctrl_attrs_name, refs=jnt_hand) self.ctrl_attrs.setParent(self.grp_anm) pymel.parentConstraint(jnt_hand, self.ctrl_attrs.offset) pymel.addAttr(self.ctrl_attrs, longName=self.kAttrName_State, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=1, k=True) pymel.connectAttr(self.ctrl_attrs.attr(self.kAttrName_State), self.grp_rig.attr(self.kAttrName_State)) # Create a chain for blending ikChain and fkChain chain_blend = pymel.duplicate(list(self.chain_jnt), renameChildren=True, parentOnly=True) for input_, node in zip(self.chain_jnt, chain_blend): blend_nomenclature = nomenclature_rig.rebuild(input_.name()) node.rename(blend_nomenclature.resolve('blend')) # Blend ikChain with fkChain constraint_ik_chain = self.sysIK._chain_ik if getattr(self.sysIK, '_chain_quad_ik', None): constraint_ik_chain = self.sysIK._chain_quad_ik for blend, oIk, oFk in zip(chain_blend, constraint_ik_chain, self.sysFK.ctrls): constraint = pymel.parentConstraint(oIk, oFk, blend) attr_weight_ik, attr_weight_fk = constraint.getWeightAliasList() pymel.connectAttr(attr_ik_weight, attr_weight_ik) pymel.connectAttr(attr_fk_weight, attr_weight_fk) chain_blend[0].setParent(self.grp_rig) # # Create elbow chain # This provide the elbow ctrl, an animator friendly way of cheating the elbow on top of the blend chain. # # Create a chain that provide the elbow controller and override the blend chain # (witch should only be nodes already) chain_elbow = pymel.duplicate(self.chain_jnt[:self.sysIK.iCtrlIndex + 1], renameChildren=True, parentOnly=True) for input_, node in zip(self.chain_jnt, chain_elbow): nomenclature_elbow = nomenclature_rig.rebuild(input_.name()) node.rename(nomenclature_elbow.resolve('elbow')) # todo: find a better name??? chain_elbow[0].setParent(self.grp_rig) # Create elbow ctrl # Note that this only affect the chain until @iCtrlIndex for i in range(1, self.sysIK.iCtrlIndex): ctrl_elbow_name = nomenclature_anm.resolve('elbow{:02}'.format(i)) ctrl_elbow_parent = chain_blend[i] if not isinstance(self.ctrl_elbow, self._CLASS_CTRL_ELBOW): self.ctrl_elbow = self._CLASS_CTRL_ELBOW(create_offset=True) ctrl_elbow_ref = self.chain_jnt[i] # jnt_elbow self.ctrl_elbow.build(refs=ctrl_elbow_ref) self.ctrl_elbow.rename(ctrl_elbow_name) self.ctrl_elbow.setParent(self.grp_anm) pymel.parentConstraint(ctrl_elbow_parent, self.ctrl_elbow.offset, maintainOffset=False) pymel.pointConstraint(chain_blend[0], chain_elbow[0], maintainOffset=False) pymel.aimConstraint(self.ctrl_elbow, chain_elbow[i-1], worldUpType=2, worldUpObject=chain_blend[i-1]) # Object Rotation Up pymel.aimConstraint(chain_blend[i+1], chain_elbow[i], worldUpType=2, worldUpObject=chain_blend[i]) # Object Rotation Up pymel.pointConstraint(self.ctrl_elbow, chain_elbow[i], maintainOffset=False) # Constraint the last elbow joint on the blend joint at the ctrl index pymel.parentConstraint(chain_blend[self.sysIK.iCtrlIndex], chain_elbow[self.sysIK.iCtrlIndex]) # Constraint input chain # Note that we only constraint to the elbow chain until @iCtrlIndex. # Afterward we constraint to the blend chain. for i in range(self.sysIK.iCtrlIndex): inn = self.chain_jnt[i] ref = chain_elbow[i] pymel.parentConstraint(ref, inn, maintainOffset=True) # todo: set to maintainOffset=False? for i in range(self.sysIK.iCtrlIndex, len(self.chain_jnt)): inn = self.chain_jnt[i] ref = chain_blend[i] pymel.parentConstraint(ref, inn, maintainOffset=True) # todo: set to maintainOffset=False? # Connect visibility pymel.connectAttr(attr_ik_weight, self.sysIK.grp_anm.visibility) pymel.connectAttr(attr_fk_weight, self.sysFK.grp_anm.visibility) # Connect globalScale pymel.connectAttr(self.grp_rig.globalScale, self.sysIK.grp_rig.globalScale, force=True) self.globalScale = self.grp_rig.globalScale # Expose the attribute, the rig will reconise it. # Parent sub-modules so they are affected by displayLayer assignment and such. self.sysIK.grp_anm.setParent(self.grp_anm) self.sysIK.grp_rig.setParent(self.grp_rig) self.sysFK.grp_anm.setParent(self.grp_anm) for sys_twist in self.sys_twist: if sys_twist.create_bend: sys_twist.grp_anm.setParent(self.grp_anm) sys_twist.grp_rig.setParent(self.grp_rig) self.attState = attr_ik_weight # Expose state
def build(self, module, ref, ref_tm=None, grp_rig=None, obj_mesh=None, u_coord=None, v_coord=None, flip_lr=False, follow_mesh=True, **kwargs): """ Create an Interactive controller that follow a geometry. :param module: ??? :param ref: :param ref_tm: :param grp_rig: :param obj_mesh: :param u_coord: :param v_coord: :param kwargs: :return: """ # HACK: Ensure flipped shapes are correctly restaured... # This is necessary since when holded, the scale of the ctrl is set to identity. # However ctrl from the right side have an inverted scale on the x axis. -_- if flip_lr and libPymel.is_valid_PyNode(self.shapes): self.shapes.sx.set(-1) pymel.makeIdentity(self.shapes, rotate=True, scale=True, apply=True) # todo: Simplify the setup, too many nodes super(InteractiveCtrl, self).build(**kwargs) #nomenclature_anm = self.get_nomenclature_anm(parent) nomenclature_rig = module.rig.nomenclature( suffix=module.rig.nomenclature.type_rig) #nomenclature_rig = self.get_nomenclature_rig(parent) # TODO: Only use position instead of PyNode or Matrix? if ref_tm is None: ref_tm = ref.getMatrix(worldSpace=True) pos_ref = ref_tm.translate # Resolve u and v coordinates # todo: check if we really want to resolve the u and v ourself since it's now connected. if obj_mesh is None: # We'll scan all available geometries and use the one with the shortest distance. meshes = libRigging.get_affected_geometries(ref) meshes = list(set(meshes) & set(module.rig.get_meshes())) obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes( meshes, pos_ref) else: _, out_u, out_v = libRigging.get_closest_point_on_shape( obj_mesh, pos_ref) if u_coord is None: u_coord = out_u if v_coord is None: v_coord = out_v if obj_mesh is None: raise Exception( "Can't find mesh affected by {0}. Skipping doritos ctrl setup." ) if self.jnt: module.debug( 'Creating doritos on {0} using {1} as reference'.format( obj_mesh, self.jnt)) else: module.debug('Creating doritos on {0}'.format(obj_mesh)) # Initialize external stack # Normally this would be hidden from animators. stack_name = nomenclature_rig.resolve('doritosStack') stack = classNode.Node(self) stack.build(name=stack_name) stack.setTranslation(pos_ref) # Add sensibility attributes # The values will be computed when attach_ctrl will be called libAttr.addAttr_separator(module.grp_rig, "ctrlCalibration") self.attr_sensitivity_tx = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TX, defaultValue=1.0) self.attr_sensitivity_ty = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TY, defaultValue=1.0) self.attr_sensitivity_tz = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TZ, defaultValue=1.0) self.attr_sensitivity_tx.set(channelBox=True) self.attr_sensitivity_ty.set(channelBox=True) self.attr_sensitivity_tz.set(channelBox=True) # Note that to only check in the Z axis, we'll do a raycast first. # If we success this will become our reference position. ''' pos = pos_ref pos.z = 999 dir = pymel.datatypes.Point(0,0,-1) result = next(iter(libRigging.ray_cast(pos, dir, [obj_mesh])), None) if result: pos_ref = result ctrl_tm.translate = result ''' # Create the layer_fol that will follow the geometry layer_fol_name = nomenclature_rig.resolve('doritosFol') layer_fol = stack.append_layer() layer_fol.rename(layer_fol_name) #layer_fol.setParent(self.grp_rig) # TODO: Validate that we don't need to inverse the rotation separately. fol_mesh = None if follow_mesh: fol_name = nomenclature_rig.resolve('doritosFollicle') fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord) fol_mesh = fol_shape.getParent() self.follicle = fol_mesh fol_mesh.rename(fol_name) pymel.parentConstraint(fol_mesh, layer_fol, maintainOffset=True) fol_mesh.setParent(self.grp_rig) # HACK: Fix rotation issues. # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since # any deformation can affect the normal of the faces. jnt_head = module.rig.get_head_jnt() if jnt_head: pymel.disconnectAttr(layer_fol.rx) pymel.disconnectAttr(layer_fol.ry) pymel.disconnectAttr(layer_fol.rz) pymel.orientConstraint(jnt_head, layer_fol, maintainOffset=True) else: self.follicle = layer_fol pymel.parentConstraint(ref, layer_fol, maintainOffset=True) # # Constraint a specic controller to the avar doritos stack. # Call this method after connecting the ctrl to the necessary avars. # The sensibility of the doritos will be automatically computed in this step if necessary. # # Create inverted attributes for sensibility util_sensitivity_inv = libRigging.create_utility_node( 'multiplyDivide', operation=2, input1X=1.0, input1Y=1.0, input1Z=1.0, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz) attr_sensibility_lr_inv = util_sensitivity_inv.outputX attr_sensibility_ud_inv = util_sensitivity_inv.outputY attr_sensibility_fb_inv = util_sensitivity_inv.outputZ # Add an inverse node that will counter animate the position of the ctrl. # TODO: Rename layer_doritos_name = nomenclature_rig.resolve('doritosInv') layer_doritos = pymel.createNode('transform', name=layer_doritos_name) layer_doritos.setParent(stack.node) # Create inverse attributes for the ctrl attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide', input1=self.node.t, input2=[-1, -1, -1]).output attr_ctrl_inv_r = libRigging.create_utility_node('multiplyDivide', input1=self.node.r, input2=[-1, -1, -1]).output attr_ctrl_inv_t = libRigging.create_utility_node( 'multiplyDivide', input1=attr_ctrl_inv_t, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz).output if flip_lr: attr_doritos_tx = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_ctrl_inv_t.outputX, input2X=-1).outputX else: attr_doritos_tx = attr_ctrl_inv_t.outputX attr_doritos_ty = attr_ctrl_inv_t.outputY attr_doritos_tz = attr_ctrl_inv_t.outputZ pymel.connectAttr(attr_doritos_tx, layer_doritos.tx) pymel.connectAttr(attr_doritos_ty, layer_doritos.ty) pymel.connectAttr(attr_doritos_tz, layer_doritos.tz) pymel.connectAttr(attr_ctrl_inv_r, layer_doritos.r) # Apply scaling on the ctrl parent. # This is were the 'black magic' happen. if flip_lr: attr_ctrl_offset_sx_inn = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_sensitivity_tx, input2X=-1).outputX else: attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz pymel.connectAttr(attr_ctrl_offset_sx_inn, self.offset.scaleX) pymel.connectAttr(attr_ctrl_offset_sy_inn, self.offset.scaleY) pymel.connectAttr(attr_ctrl_offset_sz_inn, self.offset.scaleZ) # Apply sensibility on the ctrl shape ctrl_shape = self.node.getShape() tmp = pymel.duplicate(self.node.getShape())[0] ctrl_shape_orig = tmp.getShape() ctrl_shape_orig.setParent(self.node, relative=True, shape=True) ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name())) pymel.delete(tmp) ctrl_shape_orig.intermediateObject.set(True) for cp in ctrl_shape.cp: cp.set(0, 0, 0) # Counter-scale the shape attr_adjustement_sx_inn = attr_sensibility_lr_inv attr_adjustement_sy_inn = attr_sensibility_ud_inv attr_adjustement_sz_inn = attr_sensibility_fb_inv attr_adjustement_scale = libRigging.create_utility_node( 'composeMatrix', inputScaleX=attr_adjustement_sx_inn, inputScaleY=attr_adjustement_sy_inn, inputScaleZ=attr_adjustement_sz_inn).outputMatrix attr_adjustement_rot = libRigging.create_utility_node( 'composeMatrix', inputRotateX=self.node.rotateX, inputRotateY=self.node.rotateY, inputRotateZ=self.node.rotateZ).outputMatrix attr_adjustement_rot_inv = libRigging.create_utility_node( 'inverseMatrix', inputMatrix=attr_adjustement_rot).outputMatrix attr_adjustement_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=[ attr_adjustement_rot, attr_adjustement_scale, attr_adjustement_rot_inv ]).matrixSum attr_transform_geometry = libRigging.create_utility_node( 'transformGeometry', transform=attr_adjustement_tm, inputGeometry=ctrl_shape_orig.local).outputGeometry pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True) # Constraint ctrl pymel.parentConstraint(layer_doritos, self.offset, maintainOffset=False, skipRotate=['x', 'y', 'z']) pymel.orientConstraint(layer_doritos.getParent(), self.offset, maintainOffset=True) # Clean dag junk if grp_rig: stack.setParent(grp_rig) if fol_mesh: fol_mesh.setParent(grp_rig)
def build(self, rig, *args, **kwargs): super(Limb, self).build(rig, *args, **kwargs) nomenclature_anm = self.get_nomenclature_anm(rig) nomenclature_rig = self.get_nomenclature_rig(rig) # Create IK system if not isinstance(self.sysIK, self._CLASS_SYS_IK): self.sysIK = self._CLASS_SYS_IK(self.chain_jnt) self.sysIK.name = '{0}_Ik'.format(self.name) # Hack self.sysIK.build(rig, constraint=False, **kwargs) # Create FK system if not isinstance(self.sysFK, self._CLASS_SYS_FK): self.sysFK = self._CLASS_SYS_FK(self.chain_jnt) self.sysFK.name = '{0}_Fk'.format(self.name) # Hack self.sysFK.build(rig, constraint=False, **kwargs) #Lock X and Y axis on the elbow/knee ctrl libAttr.lock_hide_rotation(self.sysFK.ctrls[1], z=False) # Store the offset between the ik ctrl and it's joint equivalent. # Useful when they don't match for example on a leg setup. self.offset_ctrl_ik = self.sysIK.ctrl_ik.getMatrix(worldSpace=True) * self.chain_jnt[self.iCtrlIndex].getMatrix(worldSpace=True).inverse() # Add attributes to the attribute holder. # Add ikFk state attribute on the grp_rig. # This is currently controlled by self.ctrl_attrs. pymel.addAttr(self.grp_rig, longName=self.kAttrName_State, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=1, k=True) attr_ik_weight = self.grp_rig.attr(self.kAttrName_State) attr_fk_weight = libRigging.create_utility_node('reverse', inputX=attr_ik_weight).outputX # Create attribute holder (this is where the IK/FK attribute will be stored) # Note that this is production specific and should be defined in a sub-class implementation. jnt_hand = self.chain_jnt[self.sysIK.iCtrlIndex] ctrl_attrs_name = nomenclature_anm.resolve('atts') if not isinstance(self.ctrl_attrs, self._CLASS_CTRL_ATTR): self.ctrl_attrs = self._CLASS_CTRL_ATTR() self.ctrl_attrs.build(rig, name=ctrl_attrs_name, refs=jnt_hand) self.ctrl_attrs.setParent(self.grp_anm) pymel.parentConstraint(jnt_hand, self.ctrl_attrs.offset) pymel.addAttr(self.ctrl_attrs, longName=self.kAttrName_State, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=1, k=True) pymel.connectAttr(self.ctrl_attrs.attr(self.kAttrName_State), self.grp_rig.attr(self.kAttrName_State)) # Create a chain for blending ikChain and fkChain chain_blend = pymel.duplicate(list(self.chain_jnt), renameChildren=True, parentOnly=True) for input_, node in zip(self.chain_jnt, chain_blend): blend_nomenclature = nomenclature_rig.rebuild(input_.name()) node.rename(blend_nomenclature.resolve('blend')) # Blend ikChain with fkChain for blend, oIk, oFk in zip(chain_blend, self.sysIK._chain_ik, self.sysFK.ctrls): constraint = pymel.parentConstraint(oIk, oFk, blend) attr_weight_ik, attr_weight_fk = constraint.getWeightAliasList() pymel.connectAttr(attr_ik_weight, attr_weight_ik) pymel.connectAttr(attr_fk_weight, attr_weight_fk) chain_blend[0].setParent(self.grp_rig) # # Create elbow chain # This provide the elbow ctrl, an animator friendly way of cheating the elbow on top of the blend chain. # # Create a chain that provide the elbow controller and override the blend chain # (witch should only be nodes already) chain_elbow = pymel.duplicate(self.chain_jnt[:self.sysIK.iCtrlIndex], renameChildren=True, parentOnly=True) for input_, node in zip(self.chain_jnt, chain_elbow): nomenclature_elbow = nomenclature_rig.rebuild(input_.name()) node.rename(nomenclature_elbow.resolve('elbow')) # todo: find a better name??? chain_elbow[0].setParent(self.grp_rig) # Create elbow ctrl # Note that this only affect the chain until @iCtrlIndex index_elbow = 1 ctrl_elbow_name = nomenclature_anm.resolve('elbow') ctrl_elbow_parent = chain_blend[index_elbow] if not isinstance(self.ctrl_elbow, self._CLASS_CTRL_ELBOW): self.ctrl_elbow = self._CLASS_CTRL_ELBOW(create_offset=True) ctrl_elbow_ref = self.chain_jnt[self.iCtrlIndex - 1] # jnt_elbow self.ctrl_elbow.build(rig, refs=ctrl_elbow_ref) self.ctrl_elbow.rename(ctrl_elbow_name) self.ctrl_elbow.setParent(self.grp_anm) pymel.parentConstraint(ctrl_elbow_parent, self.ctrl_elbow.offset, maintainOffset=False) pymel.pointConstraint(chain_blend[0], chain_elbow[0], maintainOffset=False) pymel.aimConstraint(self.ctrl_elbow, chain_elbow[0], worldUpType=2, worldUpObject=chain_blend[0]) # Object Rotation Up pymel.aimConstraint(chain_blend[self.sysIK.iCtrlIndex], chain_elbow[index_elbow], worldUpType=2, worldUpObject=chain_blend[index_elbow]) # Object Rotation Up pymel.pointConstraint(self.ctrl_elbow, chain_elbow[index_elbow], maintainOffset=False) # Constraint input chain # Note that we only constraint to the elbow chain until @iCtrlIndex. # Afterward we constraint to the blend chain. for i in range(self.sysIK.iCtrlIndex): inn = self.chain_jnt[i] ref = chain_elbow[i] pymel.parentConstraint(ref, inn, maintainOffset=True) # todo: set to maintainOffset=False? for i in range(self.sysIK.iCtrlIndex, len(self.chain_jnt)): inn = self.chain_jnt[i] ref = chain_blend[i] pymel.parentConstraint(ref, inn, maintainOffset=True) # todo: set to maintainOffset=False? # Connect visibility pymel.connectAttr(attr_ik_weight, self.sysIK.grp_anm.visibility) pymel.connectAttr(attr_fk_weight, self.sysFK.grp_anm.visibility) # Connect globalScale pymel.connectAttr(self.grp_rig.globalScale, self.sysIK.grp_rig.globalScale, force=True) self.globalScale = self.grp_rig.globalScale # Expose the attribute, the rig will reconise it. # Parent sub-modules so they are affected by displayLayer assignment and such. self.sysIK.grp_anm.setParent(self.grp_anm) self.sysIK.grp_rig.setParent(self.grp_rig) self.sysFK.grp_anm.setParent(self.grp_anm) self.attState = attr_ik_weight # Expose state
def create(arg1, arg2): u = libRigging.create_utility_node('multiplyDivide', input1X=arg1, input2X=arg2) u.operation.set(2) # HACK: Prevent division by zero by changing the operator at the last second. return u.outputX
def build(self, *args, **kwargs): super(Hand, self).build(parent=False, *args, **kwargs) # Resolve fingers and metacarpals #jnts_metacarpals = [] #metacarpals_sys = [] meta_index = 0 #We can have less metacarpals than finger chains nomenclature_anm = self.get_nomenclature_anm() nomenclature_rig = self.get_nomenclature_rig() # Create fingers systems if necessary for i, chain in enumerate(self.chains): chain_length = len(chain) # Skip unsupported chain length if chain_length > 5: logging.warning("Unsupported chain length for {0}. Expected 4 or less, got {1}".format( chain, chain_length )) continue # Resolve phalanges and metacarpal from chain if chain_length == 5: jnt_metacarpal = chain[0] jnts_phalanges = chain[1:-1] #jnts_metacarpals.append(jnt_metacarpal) else: jnt_metacarpal = None jnts_phalanges = chain[:-1] # Rig metacarpals if necessary ctrl_meta = None if jnt_metacarpal: if meta_index >= len(self.fk_sys_metacarpals): ctrl_meta = rigFK.FK([jnt_metacarpal], rig=self.rig) ctrl_meta.name = ctrl_meta.get_default_name() self.fk_sys_metacarpals.append(ctrl_meta) ctrl_meta = self.fk_sys_metacarpals[meta_index] ctrl_meta.create_spaceswitch = False ctrl_meta.build() ctrl_meta.grp_anm.setParent(self.grp_anm) meta_index += 1 # Rig fingers if not self.sysFingers or i >= len(self.sysFingers): sysFinger = rigFK.AdditiveFK(jnts_phalanges, rig=self.rig) sysFinger.name = sysFinger.get_default_name() self.sysFingers.append(sysFinger) sysFinger = self.sysFingers[i] sysFinger.create_spaceswitch = False sysFinger.build() if ctrl_meta: sysFinger.grp_anm.setParent(ctrl_meta.ctrls[0]) else: sysFinger.grp_anm.setParent(self.grp_anm) #Keep the system to make sure it match the index of the metacarpal associated #metacarpals_sys.append(sysFinger) # Rig the 'cup' setup if self.fk_sys_metacarpals: pos_inn = self.fk_sys_metacarpals[0].ctrls[0].getTranslation(space='world') pos_out = self.fk_sys_metacarpals[-1].ctrls[0].getTranslation(space='world') pos_mid = ((pos_out - pos_inn) / 2.0) + pos_inn # Resolve the metacarpal plane orientation parent_tm = self.parent.getMatrix(worldSpace=True) x = pos_out - pos_inn x.normalize() y = pymel.datatypes.Vector(parent_tm.a10, parent_tm.a11, parent_tm.a12) z = x.cross(y) #Get the front vector z.normalize() y = x.cross(z) #Ensure the up vector is orthogonal y.normalize() ref_tm = pymel.datatypes.Matrix( z.x, z.y, z.z, 0.0, y.x, y.y, y.z, 0.0, x.x, x.y, x.z, 0.0, pos_mid.x, pos_mid.y, pos_mid.z, 1.0 ) rig_metacarpal_center = pymel.spaceLocator(name=nomenclature_rig.resolve('metacarpCenter')) rig_metacarpal_center.setMatrix(ref_tm) rig_metacarpal_center.setParent(self.grp_rig) pymel.parentConstraint(self.parent, rig_metacarpal_center, maintainOffset=True) # Create the 'cup' attribute attr_holder = self.grp_anm pymel.addAttr(attr_holder, longName='cup', keyable=True) attr_cup = attr_holder.attr('cup') for i, ctrl_metacarpal in enumerate(self.fk_sys_metacarpals): width = pos_inn.distanceTo(pos_out) pos = ctrl_metacarpal.ctrls[0].getTranslation(space='world') ratio = (pos - pos_inn).length() / width grp_pivot_name = nomenclature_rig.resolve(ctrl_metacarpal.input[0].name() + '_pivot') grp_pivot = pymel.createNode('transform', name=grp_pivot_name) grp_pivot.setMatrix(ref_tm) grp_pivot.setParent(rig_metacarpal_center) multiplier = (ratio * 2.0) - 1.0 attr_rotate_x = libRigging.create_utility_node('multiplyDivide', input1X=attr_cup, input2X=multiplier, ).outputX pymel.connectAttr(attr_rotate_x, grp_pivot.rotateY) # jnt_phalange_inn = next(iter(jnt_metacarpal.getChildren())) # grp_pos = pymel.createNode('transform') # grp_pos.rename(nomenclature_rig.resolve(jnt_phalange_inn.name() + "_parentPos")) # grp_pos.setMatrix(jnt_phalange_inn.getMatrix(worldSpace=True)) # grp_pos.setParent(grp_pivot) # Note that the cup system worked with a partial parentConstraint. # I've remove it since it was breaking things. # TODO: Make it work again. pymel.parentConstraint(grp_pivot, ctrl_metacarpal.ctrls[0].offset, maintainOffset=True) # HACK: Override the phalanges rig parent # sysFinger = metacarpals_sys[i] # pymel.disconnectAttr(sysFinger.grp_anm.translateX) # pymel.disconnectAttr(sysFinger.grp_anm.translateY) # pymel.disconnectAttr(sysFinger.grp_anm.translateZ) # pymel.parentConstraint(grp_pivot, sysFinger.grp_anm, maintainOffset=True, skipRotate=['x', 'y', 'z']) # pymel.pointConstraint(grp_pos, sysFinger.grp_anm, maintainOffset=True) #Connect Global scale pymel.connectAttr(self.grp_rig.globalScale, self.grp_rig.scaleX) pymel.connectAttr(self.grp_rig.globalScale, self.grp_rig.scaleY) pymel.connectAttr(self.grp_rig.globalScale, self.grp_rig.scaleZ) pymel.parentConstraint(self.parent, self.grp_anm, maintainOffset=True)
def create(arg1, arg2): return libRigging.create_utility_node('plusMinusAverage', operation=2, input1D=[arg1, arg2]).output1D
def build(self, **kwargs): """ Build function for the softik node :return: Nothing """ super(SoftIkNode, self).build(**kwargs) formula = libFormula.Formula() fn_add_attr = functools.partial(libAttr.addAttr, self.node, hasMinValue=True, hasMaxValue=True) formula.inMatrixS = fn_add_attr(longName='inMatrixS', dt='matrix') formula.inMatrixE = fn_add_attr(longName='inMatrixE', dt='matrix') formula.inRatio = fn_add_attr(longName='inRatio', at='float') formula.inStretch = fn_add_attr(longName='inStretch', at='float') formula.inChainLength = fn_add_attr(longName='inChainLength', at='float', defaultValue=1.0) # inDistance is the distance between the start of the chain and the ikCtrl formula.inDistance = "inMatrixS~inMatrixE" # distanceSoft is the distance before distanceMax where the softIK kick in. # ex: For a chain of length 10.0 with a ratio of 0.1, the distanceSoft will be 1.0. formula.distanceSoft = "inChainLength*inRatio" # distanceSafe is the distance where there's no softIK. # ex: For a chain of length 10.0 with a ratio of 0.1, the distanceSafe will be 9.0. formula.distanceSafe = "inChainLength-distanceSoft" # This represent the soft-ik state # When the soft-ik kick in, the value is 0.0. # When the stretch kick in, the value is 1.0. # |-----------|-----------|----------| # -1 0.0 1.0 +++ # -dBase dSafe dMax # Hack: Prevent potential division by zero. # Originally we were using a condition, however in Maya 2016+ in Parallel or Serial evaluation mode, this # somehow evalated the division even when the condition was False. formula.distanceSoftClamped = libRigging.create_utility_node( 'clamp', inputR=formula.distanceSoft, minR=0.0001, maxR=999).outputR formula.deltaSafeSoft = "(inDistance-distanceSafe)/distanceSoftClamped" # outDistanceSoft is the desired ikEffector distance from the chain start after aplying the soft-ik # If there's no stretch, this will be directly applied to the ikEffector. # If there's stretch, this will be used to compute the amount of stretch needed to reach the ikCtrl # while preserving the shape. formula.outDistanceSoft = "(distanceSoft*(1-(e^(deltaSafeSoft*-1))))+distanceSafe" # Affect ikEffector distance only where inDistance if bigger than distanceSafe. formula.outDistance = libRigging.create_utility_node( 'condition', operation=2, firstTerm=formula.deltaSafeSoft, secondTerm=0.0, colorIfTrueR=formula.outDistanceSoft, colorIfFalseR=formula.inDistance).outColorR # Affect ikEffector when we're not using stretching formula.outDistance = libRigging.create_utility_node( 'blendTwoAttr', input=[formula.outDistance, formula.inDistance], attributesBlender=formula.inStretch).output # # Handle Stretching # # If we're using softIk AND stretchIk, we'll use the outRatioSoft to stretch the joints enough so # that the ikEffector reach the ikCtrl. formula.outStretch = "inDistance/outDistanceSoft" # Apply the softIK only AFTER the distanceSafe formula.outStretch = libRigging.create_utility_node( 'condition', operation=2, firstTerm=formula.inDistance, secondTerm=formula.distanceSafe, colorIfTrueR=formula.outStretch, colorIfFalseR=1.0).outColorR # Apply stretching only if inStretch is ON formula.outStretch = libRigging.create_utility_node( 'blendTwoAttr', input=[1.0, formula.outStretch], attributesBlender=formula.inStretch).output # # Connect outRatio and outStretch to our softIkNode # # fnAddAttr(longName='outTranslation', dt='float3') formula.outRatio = "outDistance/inDistance" attr_ratio = fn_add_attr(longName='outRatio', at='float') pymel.connectAttr(formula.outRatio, attr_ratio) attr_stretch = fn_add_attr(longName='outStretch', at='float') pymel.connectAttr(formula.outStretch, attr_stretch)
def setup_softik(self, ik_handle_to_constraint, stretch_chain): """ Setup the softik system a ik system :param ik_handle_to_constraint: The ik handle to constraint on the soft ik network (Can be more than one) :param stretch_chain: The chain on which the stretch will be connected :return: Nothing """ nomenclature_rig = self.get_nomenclature_rig() oAttHolder = self.ctrl_ik fnAddAttr = functools.partial(libAttr.addAttr, hasMinValue=True, hasMaxValue=True) attInRatio = fnAddAttr(oAttHolder, longName='softIkRatio', niceName='SoftIK', defaultValue=0, minValue=0, maxValue=50, k=True) attInStretch = fnAddAttr(oAttHolder, longName='stretch', niceName='Stretch', defaultValue=0, minValue=0, maxValue=1.0, k=True) # Adjust the ratio in percentage so animators understand that 0.03 is 3% attInRatio = libRigging.create_utility_node('multiplyDivide', input1X=attInRatio, input2X=0.01).outputX # Create and configure SoftIK solver soft_ik_network_name = nomenclature_rig.resolve('softik') soft_ik_network = SoftIkNode() soft_ik_network.build(name=soft_ik_network_name) soft_ik_network.setParent(self.grp_rig) pymel.connectAttr(attInRatio, soft_ik_network.inRatio) pymel.connectAttr(attInStretch, soft_ik_network.inStretch) pymel.connectAttr(self._ikChainGrp.worldMatrix, soft_ik_network.inMatrixS) pymel.connectAttr(self._ik_handle_target.worldMatrix, soft_ik_network.inMatrixE) attr_distance = libFormula.parse('distance*globalScale', distance=self.chain_length, globalScale=self.grp_rig.globalScale) pymel.connectAttr(attr_distance, soft_ik_network.inChainLength) attOutRatio = soft_ik_network.outRatio attOutRatioInv = libRigging.create_utility_node( 'reverse', inputX=soft_ik_network.outRatio).outputX #TODO: Improve softik ratio when using multiple ik handle. Not the same ratio will be used depending of the angle for handle in ik_handle_to_constraint: pointConstraint = pymel.pointConstraint(self._ik_handle_target, self._ikChainGrp, handle) pointConstraint.rename(pointConstraint.name().replace( 'pointConstraint', 'softIkConstraint')) weight_inn, weight_out = pointConstraint.getWeightAliasList()[ -2:] #Ensure to get the latest target added pymel.connectAttr(attOutRatio, weight_inn) pymel.connectAttr(attOutRatioInv, weight_out) # Connect stretch for i in range(1, self.iCtrlIndex + 1): obj = stretch_chain[i] util_get_t = libRigging.create_utility_node( 'multiplyDivide', input1X=soft_ik_network.outStretch, input1Y=soft_ik_network.outStretch, input1Z=soft_ik_network.outStretch, input2=obj.t.get()) pymel.connectAttr(util_get_t.outputX, obj.tx, force=True) pymel.connectAttr(util_get_t.outputY, obj.ty, force=True) pymel.connectAttr(util_get_t.outputZ, obj.tz, force=True) return soft_ik_network
def create(arg1, arg2): return libRigging.create_utility_node('multiplyDivide', operation=3, input1X=arg1, input2X=arg2).outputX
def create(arg1, arg2): log.debug('[equal:create] {0} * {1}'.format(arg1, arg2)) return libRigging.create_utility_node('condition', operation=0, colorIfTrue=1.0, colorIfFalse=0.0).outColorR
def build(self, avar, ref=None, ref_tm=None, grp_rig=None, obj_mesh=None, u_coord=None, v_coord=None, flip_lr=False, follow_mesh=True, ctrl_tm=None, ctrl_size=1.0, parent_pos=None, parent_rot=None, parent_scl=None, constraint=False, cancel_t=True, cancel_r=True, attr_bind_tm=None, **kwargs): # todo: get rid of the u_coods, v_coods etc, we should rely on the bind super(ModelCtrlLinear, self).build(avar, ctrl_size=ctrl_size, **kwargs) nomenclature_rig = self.get_nomenclature_rig() # # Resolve necessary informations # # Resolve which object will the InteractiveCtrl track. # If we don't want to follow a particular geometry, we'll use the end of the stack. # Otherwise the influence will be used (to also resolve the geometry). # todo: it could be better to resolve the geometry ourself if ref is None: ref = self.jnt # Resolve the ctrl default tm if ctrl_tm is None: ctrl_tm = self.get_default_tm_ctrl() if ctrl_tm is None: raise Exception("Cannot resolve ctrl transformation matrix!") # By default, we expect the rigger to mirror the face joints using the 'behavior' mode. if flip_lr: ctrl_tm = pymel.datatypes.Matrix( 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0) * ctrl_tm self._grp_bind_ctrl = pymel.createNode( 'transform', name=nomenclature_rig.resolve('ctrlBindTm'), parent=self.grp_rig, ) self._grp_bind_ctrl.setMatrix(ctrl_tm) # Resolve the influence bind tm # Create an offset node to easily change it. self._grp_bind_infl = pymel.createNode('transform', name=nomenclature_rig.resolve('bind'), parent=self.grp_rig) if attr_bind_tm: util_decompose_bind = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_bind_tm, ) pymel.connectAttr(util_decompose_bind.outputTranslate, self._grp_bind_infl.translate) pymel.connectAttr(util_decompose_bind.outputRotate, self._grp_bind_infl.rotate) pymel.connectAttr(util_decompose_bind.outputScale, self._grp_bind_infl.scale) else: # todo: deprecate this? self._grp_bind_infl.setTranslation(ctrl_tm.translate) # Create a follicle, this will be used for calibration purpose. # If this affect performance we can create it only when necessary, however being able to # see it help with debugging. follicle_transform, follicle_shape = self._create_follicle( ctrl_tm, ref, obj_mesh=obj_mesh, u_coord=u_coord, v_coord=v_coord, ) self.follicle = follicle_transform # # Add calibration-related attribute # # The values will be computed when attach_ctrl will be called libAttr.addAttr_separator( self.grp_rig, "ctrlCalibration" ) self.attr_sensitivity_tx = libAttr.addAttr( self.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TX, defaultValue=1.0 ) self.attr_sensitivity_ty = libAttr.addAttr( self.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TY, defaultValue=1.0 ) self.attr_sensitivity_tz = libAttr.addAttr( self.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TZ, defaultValue=1.0 ) self.attr_sensitivity_tx.set(channelBox=True) self.attr_sensitivity_ty.set(channelBox=True) self.attr_sensitivity_tz.set(channelBox=True) # Hack: Since there's scaling on the ctrl so the left and right side ctrl channels matches, we need to flip the ctrl shapes. if flip_lr: self.ctrl.scaleX.set(-1) libPymel.makeIdentity_safe(self.ctrl, rotate=True, scale=True, apply=True) grp_output = pymel.createNode( 'transform', name=nomenclature_rig.resolve('output'), parent=self.grp_rig ) attr_output_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=( self._grp_bind_ctrl.matrix, self._attr_inn_parent_tm, self.rig.grp_anm.worldInverseMatrix ) ).matrixSum libRigging.connect_matrix_to_node(attr_output_tm, grp_output) # Create inverted attributes for sensibility util_sensitivity_inv = libRigging.create_utility_node( 'multiplyDivide', operation=2, input1X=1.0, input1Y=1.0, input1Z=1.0, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz ) attr_sensibility_lr_inv = util_sensitivity_inv.outputX attr_sensibility_ud_inv = util_sensitivity_inv.outputY attr_sensibility_fb_inv = util_sensitivity_inv.outputZ attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz # Connect any additionel scale source. if parent_scl: u = libRigging.create_utility_node( 'multiplyDivide', name=nomenclature_rig.resolve('getAbsoluteCtrlOffsetScale'), input1X=attr_ctrl_offset_sx_inn, input1Y=attr_ctrl_offset_sy_inn, input1Z=attr_ctrl_offset_sz_inn, input2X=parent_scl.scaleX, input2Y=parent_scl.scaleY, input2Z=parent_scl.scaleZ ) attr_ctrl_offset_sx_inn, attr_ctrl_offset_sy_inn, attr_ctrl_offset_sz_inn = u.outputX, u.outputY, u.outputZ # Ensure the scaling of the parent is taken in account. attr_calibration_scale_tm = libRigging.create_utility_node( 'composeMatrix', name=nomenclature_rig.resolve('composeCalibrationScaleTm'), inputScaleX=attr_ctrl_offset_sx_inn, inputScaleY=attr_ctrl_offset_sy_inn, inputScaleZ=attr_ctrl_offset_sz_inn, ).outputMatrix attr_ctrl_offset_scale_tm = libRigging.create_utility_node( 'multMatrix', name=nomenclature_rig.resolve('getCtrlOffsetScaleTm'), matrixIn=( attr_calibration_scale_tm, self._attr_inn_parent_tm, self.rig.grp_anm.worldInverseMatrix, ) ).matrixSum attr_ctrl_offset_scale = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_ctrl_offset_scale_tm ).outputScale # Flip the x axis if we are on the right side of the face. # We need to do it as the last step since this will result in a right-handed matrix # which will be canceled out if we feed it into multMatrix or other maya nodes. if flip_lr: attr_ctrl_offset_scale = libRigging.create_utility_node( 'multiplyDivide', input1=attr_ctrl_offset_scale, input2X=-1.0, input2Y=1.0, input2Z=1.0, ).output pymel.connectAttr(attr_ctrl_offset_scale, self.ctrl.offset.scale) # Apply sensibility on the ctrl shape ctrl_shape = self.ctrl.node.getShape() tmp = pymel.duplicate(self.ctrl.node.getShape())[0] ctrl_shape_orig = tmp.getShape() ctrl_shape_orig.setParent(self.ctrl.node, relative=True, shape=True) ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name())) pymel.delete(tmp) ctrl_shape_orig.intermediateObject.set(True) for cp in ctrl_shape.cp: cp.set(0, 0, 0) # Counter-scale the shape attr_adjustement_sx_inn = attr_sensibility_lr_inv attr_adjustement_sy_inn = attr_sensibility_ud_inv attr_adjustement_sz_inn = attr_sensibility_fb_inv attr_adjustement_scale = libRigging.create_utility_node( 'composeMatrix', inputScaleX=attr_adjustement_sx_inn, inputScaleY=attr_adjustement_sy_inn, inputScaleZ=attr_adjustement_sz_inn ).outputMatrix attr_adjustement_rot = libRigging.create_utility_node( 'composeMatrix', inputRotateX=self.ctrl.node.rotateX, inputRotateY=self.ctrl.node.rotateY, inputRotateZ=self.ctrl.node.rotateZ ).outputMatrix attr_adjustement_rot_inv = libRigging.create_utility_node( 'inverseMatrix', inputMatrix=attr_adjustement_rot ).outputMatrix attr_adjustement_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=[ attr_adjustement_rot, attr_adjustement_scale, attr_adjustement_rot_inv ] ).matrixSum attr_transform_geometry = libRigging.create_utility_node( 'transformGeometry', transform=attr_adjustement_tm, inputGeometry=ctrl_shape_orig.local ).outputGeometry pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True) pymel.connectAttr(grp_output.translate, self.ctrl.offset.translate) pymel.connectAttr(grp_output.rotate, self.ctrl.offset.rotate) if constraint and self.jnt: pymel.parentConstraint(self.ctrl.node, self.jnt, maintainOffset=True)
def create_spaceswitch(self, module, parent, add_default=True, default_name=None, add_world=False, **kwargs): """ Create the space switch attribute on the controller using a list of target found from it's module hierarchy :param module: The module on which we want to process space switch targets :param parent: The parent used as the default (local) target :param add_default: Is the default target will be added to the list of targets :param default_name: The name of the default target :param add_world: Is the world will be added as a target :param kwargs: Additional parameters :return: None """ # TODO: Handle when parent is None? nomenclature = module.rig.nomenclature # Resolve spaceswitch targets targets, labels, indexes = self.get_spaceswitch_targets( module, parent, add_world=add_world, add_local=add_default) if not targets: module.warning( "Can't add space switch on {0}. No targets found!".format( self.node.__melobject__())) return if default_name is None: default_name = 'Local' # Resolve the niceName of the targets for i in range(len(targets)): target = targets[i] label = labels[i] if label is None and target is not None: name = nomenclature(target.name()) name.remove_extra_tokens() labels[i] = name.resolve() # Create the parent constraint before adding the local since local target will be set to itself # to keep a serialized link to the local target layer_space_switch = self.append_layer('spaceSwitch') parent_constraint = pymel.parentConstraint(targets, layer_space_switch, maintainOffset=True, **kwargs) # Build the enum string from the information we got enum_string = "" # Add the local option if needed if add_default: # We cannot self referencing since it will break maya deletion mechanism # targets.append(self) # indexes.append(default_index) # labels.append(default_name) enum_string += default_name + "=" + \ str(self.local_index) # The enum string will skip index if needed for label, index in zip(labels, indexes): if enum_string: enum_string += ":" enum_string += label + "=" + str(index) # Update the serialized variable to make sure everything is up to date for i, target in enumerate(targets): if target not in self.targets: self.targets.append(target) if indexes[i] in self.targets_indexes: log.warning( "Index ({0}) is already used for space switch on ctrl {1}. " "Strange behavior could happen".format( indexes[i], self.name())) self.targets_indexes.append(indexes[i]) attr_space = libAttr.addAttr(self.node, 'space', at='enum', enumName=enum_string, k=True) atts_weights = parent_constraint.getWeightAliasList() for i, att_weight in enumerate(atts_weights): index_to_match = indexes[i] att_enabled = libRigging.create_utility_node( #Equal 'condition', firstTerm=attr_space, secondTerm=index_to_match, colorIfTrueR=1, colorIfFalseR=0).outColorR pymel.connectAttr(att_enabled, att_weight) # By Default, the active space will be local, else root and finally fallback on the first index found if add_default: self.node.space.set(default_name) elif self._reserved_idx['root'] in self.targets_indexes: self.node.space.set(self._reserved_idx['root']) else: if self.targets_indexes: self.node.space.set(self.targets_indexes[0])
def build_stack(self, stack, **kwargs): nomenclature_rig = self.get_nomenclature_rig() jnt_head = self.rig.get_head_jnt() jnt_jaw = self.rig.get_jaw_jnt() # # Create additional attributes to control the jaw layer # libAttr.addAttr_separator(self.grp_rig, 'jawLayer') self._attr_inn_jaw_ratio_default = libAttr.addAttr(self.grp_rig, 'jawRatioDefault', defaultValue=0.5, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, k=True) self._attr_bypass_splitter = libAttr.addAttr(self.grp_rig, 'jawSplitterBypass', defaultValue=0.0, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, k=True) # # Create reference objects used for calculations. # # Create a reference node that follow the head self._target_head = pymel.createNode( 'transform', name=nomenclature_rig.resolve('innHead'), parent=self.grp_rig) self._target_head.setTranslation( jnt_head.getTranslation(space='world')) pymel.parentConstraint(jnt_head, self._target_head, maintainOffset=True) pymel.scaleConstraint(jnt_head, self._target_head, maintainOffset=True) # Create a reference node that follow the jaw initial position jaw_pos = jnt_jaw.getTranslation(space='world') self._target_jaw_bindpose = pymel.createNode( 'transform', name=nomenclature_rig.resolve('innJawBindPose'), parent=self.grp_rig) self._target_jaw_bindpose.setTranslation(jaw_pos) # Create a reference node that follow the jaw self._target_jaw = pymel.createNode( 'transform', name=nomenclature_rig.resolve('innJaw'), parent=self._target_jaw_bindpose) self._target_jaw.t.set(0, 0, 0) pymel.parentConstraint(jnt_jaw, self._target_jaw, maintainOffset=True) pymel.scaleConstraint(jnt_jaw, self._target_jaw, maintainOffset=True) # Create a node that contain the out jaw influence. # Note that the out jaw influence can be modified by the splitter node. grp_parent_pos = self._grp_parent.getTranslation( space='world') # grp_offset is always in world coordinates self._jaw_ref = pymel.createNode( 'transform', name=nomenclature_rig.resolve('outJawInfluence'), parent=self.grp_rig) self._jaw_ref.t.set(grp_parent_pos) pymel.parentConstraint(self._target_jaw, self._jaw_ref, maintainOffset=True) # Extract jaw influence attr_delta_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=[ self._jaw_ref.worldMatrix, self._grp_parent.worldInverseMatrix ]).matrixSum util_extract_jaw = libRigging.create_utility_node( 'decomposeMatrix', name=nomenclature_rig.resolve('getJawRotation'), inputMatrix=attr_delta_tm) super(FaceLipsAvar, self).build_stack(stack, **kwargs) # # Create jaw influence layer # Create a reference object to extract the jaw displacement. # # Add the jaw influence as a new stack layer. layer_jaw_r = stack.prepend_layer(name='jawRotate') layer_jaw_t = stack.prepend_layer(name='jawTranslate') pymel.connectAttr(util_extract_jaw.outputTranslate, layer_jaw_t.t) pymel.connectAttr(util_extract_jaw.outputRotate, layer_jaw_r.r)
def build(self, attr_holder=None, constraint_handle=False, setup_softik=True, **kwargs): """ Build the LegIk system :param attr_holder: The attribute holder object for all the footroll params :param kwargs: More kwargs pass to the superclass :return: Nothing """ # Compute ctrl_ik orientation # Hack: Bypass pymel bug (see https://github.com/LumaPictures/pymel/issues/355) ctrl_ik_orientation = pymel.datatypes.TransformationMatrix(self._get_reference_plane()).rotate super(LegIk, self).build(ctrl_ik_orientation=ctrl_ik_orientation, constraint_handle=constraint_handle, setup_softik=setup_softik, **kwargs) nomenclature_rig = self.get_nomenclature_rig() jnts = self._chain_ik[self.iCtrlIndex:] num_jnts = len(jnts) if num_jnts == 4: jnt_foot, jnt_heel, jnt_toes, jnt_tip = jnts elif num_jnts == 3: jnt_foot, jnt_toes, jnt_tip = jnts jnt_heel = None else: raise Exception("Unexpected number of joints after the limb. Expected 3 or 4, got {0}".format(num_jnts)) # Create FootRoll (chain?) pos_foot = pymel.datatypes.Point(jnt_foot.getTranslation(space='world')) pos_heel = pymel.datatypes.Point(jnt_heel.getTranslation(space='world')) if jnt_heel else None pos_toes = pymel.datatypes.Point(jnt_toes.getTranslation(space='world')) pos_tip = pymel.datatypes.Point(jnt_tip.getTranslation(space='world')) # Resolve pivot locations tm_ref = self._get_reference_plane() tm_ref_dir = pymel.datatypes.Matrix( # Used to compute raycast directions tm_ref.a00, tm_ref.a01, tm_ref.a02, tm_ref.a03, tm_ref.a10, tm_ref.a11, tm_ref.a12, tm_ref.a13, tm_ref.a20, tm_ref.a21, tm_ref.a22, tm_ref.a23, 0, 0, 0, 1 ) # # Resolve pivot positions # geometries = self.rig.get_meshes() # Resolve pivot inn if self.pivot_foot_inn_pos: pos_pivot_inn = pymel.datatypes.Point(self.pivot_foot_inn_pos) * tm_ref else: pos_pivot_inn = self._get_recommended_pivot_bank(geometries, tm_ref, tm_ref_dir, pos_toes, direction=-1) # Resolve pivot bank out if self.pivot_foot_out_pos: pos_pivot_out = pymel.datatypes.Point(self.pivot_foot_out_pos) * tm_ref else: pos_pivot_out = self._get_recommended_pivot_bank(geometries, tm_ref, tm_ref_dir, pos_toes, direction=1) # Resolve pivot Back if self.pivot_foot_back_pos: pos_pivot_back = pymel.datatypes.Point(self.pivot_foot_back_pos) * tm_ref else: pos_pivot_back = self._get_recommended_pivot_back(geometries, tm_ref, tm_ref_dir, pos_toes) # Set pivot Front if self.pivot_foot_front_pos: pos_pivot_front = pymel.datatypes.Point(self.pivot_foot_front_pos) * tm_ref else: pos_pivot_front = self._get_recommended_pivot_front(geometries, tm_ref, tm_ref_dir, pos_toes, pos_tip) # Set pivot Ankle if self.pivot_toes_ankle_pos: pos_pivot_ankle = pymel.datatypes.Point(self.pivot_toes_ankle_pos) * tm_ref else: pos_pivot_ankle = pos_toes # Set pivot Heel floor if self.pivot_toes_heel_pos: pos_pivot_heel = pymel.datatypes.Point(self.pivot_toes_heel_pos) * tm_ref else: if jnt_heel: pos_pivot_heel = pos_heel else: pos_pivot_heel = pymel.datatypes.Point(pos_foot) pos_pivot_heel.y = 0 # # Build Setup # root_footRoll = pymel.createNode('transform', name=nomenclature_rig.resolve('footRoll')) # Align all pivots to the reference plane root_footRoll.setMatrix(tm_ref) # Create pivots hierarchy self.pivot_toes_heel = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotToesHeel')) self.pivot_toes_ankle = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotToesAnkle')) self.pivot_foot_ankle = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotFootAnkle')) self.pivot_foot_front = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotFootFront')) self.pivot_foot_back = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotFootBack')) self.pivot_foot_inn = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotFootBankInn')) self.pivot_foot_out = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotFootBankOut')) self.pivot_foot_heel = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotFootHeel')) self.pivot_foot_toes_fk = pymel.spaceLocator(name=nomenclature_rig.resolve('pivotToesFkRoll')) chain_footroll = [ root_footRoll, self.pivot_foot_ankle, self.pivot_foot_inn, self.pivot_foot_out, self.pivot_foot_back, self.pivot_foot_heel, self.pivot_foot_front, self.pivot_toes_ankle, self.pivot_toes_heel ] libRigging.create_hyerarchy(chain_footroll) chain_footroll[0].setParent(self.grp_rig) self.pivot_foot_toes_fk.setParent(self.pivot_foot_heel) self.pivot_foot_ankle.setTranslation(pos_pivot_ankle, space='world') self.pivot_foot_inn.setTranslation(pos_pivot_inn, space='world') self.pivot_foot_out.setTranslation(pos_pivot_out, space='world') self.pivot_foot_back.setTranslation(pos_pivot_back, space='world') self.pivot_foot_heel.setTranslation(pos_pivot_heel, space='world') self.pivot_foot_front.setTranslation(pos_pivot_front, space='world') self.pivot_toes_ankle.setTranslation(pos_pivot_ankle, space='world') self.pivot_foot_toes_fk.setTranslation(pos_pivot_ankle, space='world') self.pivot_toes_heel.setTranslation(pos_pivot_heel, space='world') # Create attributes attr_holder = self.ctrl_ik libAttr.addAttr_separator(attr_holder, 'footRoll', niceName='Foot Roll') attr_inn_roll_auto = libAttr.addAttr(attr_holder, longName='rollAuto', k=True) attr_inn_roll_auto_threshold = libAttr.addAttr(attr_holder, longName='rollAutoThreshold', k=True, defaultValue=25) attr_inn_bank = libAttr.addAttr(attr_holder, longName='bank', k=True) attr_inn_ankle_rotz = libAttr.addAttr(attr_holder, longName=self.ANKLE_ROTZ_LONGNAME, niceName=self.ANKLE_ROTZ_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=-90, maxValue=90) attr_inn_back_rotx = libAttr.addAttr(attr_holder, longName=self.BACK_ROTX_LONGNAME, niceName=self.BACK_ROTX_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=-90, maxValue=0) attr_inn_ankle_rotx = libAttr.addAttr(attr_holder, longName=self.ANKLE_ROTX_LONGNAME, niceName=self.ANKLE_ROTX_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=90) attr_inn_front_rotx = libAttr.addAttr(attr_holder, longName=self.FRONT_ROTX_LONGNAME, niceName=self.FRONT_ROTX_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=90) attr_inn_back_roty = libAttr.addAttr(attr_holder, longName=self.BACK_ROTY_LONGNAME, niceName=self.BACK_ROTY_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=-90, maxValue=90) attr_inn_heel_roty = libAttr.addAttr(attr_holder, longName=self.HEEL_ROTY_LONGNAME, niceName=self.HEEL_ROTY_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=-90, maxValue=90) attr_inn_toes_roty = libAttr.addAttr(attr_holder, longName=self.TOES_ROTY_LONGNAME, niceName=self.TOES_ROTY_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=-90, maxValue=90) attr_inn_front_roty = libAttr.addAttr(attr_holder, longName=self.FRONT_ROTY_LONGNAME, niceName=self.FRONT_ROTY_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=-90, maxValue=90) attr_inn_toes_fk_rotx = libAttr.addAttr(attr_holder, longName=self.TOESFK_ROTX_LONGNAME, niceName=self.TOESFK_ROTX_NICENAME, k=True, hasMinValue=True, hasMaxValue=True, minValue=-90, maxValue=90) attr_roll_auto_pos = libRigging.create_utility_node('condition', operation=2, firstTerm=attr_inn_roll_auto, secondTerm=0, colorIfTrueR=attr_inn_roll_auto, colorIfFalseR=0.0).outColorR # Greater attr_roll_auto_f = libRigging.create_utility_node('condition', operation=2, firstTerm=attr_inn_roll_auto, secondTerm=attr_inn_roll_auto_threshold, colorIfFalseR=0, colorIfTrueR=( libRigging.create_utility_node('plusMinusAverage', operation=2, input1D=[ attr_inn_roll_auto, attr_inn_roll_auto_threshold]).output1D) ).outColorR # Substract attr_roll_auto_b = libRigging.create_utility_node('condition', operation=2, firstTerm=attr_inn_roll_auto, secondTerm=0.0, colorIfTrueR=0, colorIfFalseR=attr_inn_roll_auto ).outColorR # Greater attr_roll_m = libRigging.create_utility_node('addDoubleLinear', input1=attr_roll_auto_pos, input2=attr_inn_ankle_rotx).output attr_roll_f = libRigging.create_utility_node('addDoubleLinear', input1=attr_roll_auto_f, input2=attr_inn_front_rotx).output attr_roll_b = libRigging.create_utility_node('addDoubleLinear', input1=attr_roll_auto_b, input2=attr_inn_back_rotx).output attr_bank_inn = libRigging.create_utility_node('condition', operation=2, firstTerm=attr_inn_bank, secondTerm=0, colorIfTrueR=attr_inn_bank, colorIfFalseR=0.0 ).outColorR # Greater attr_bank_out = libRigging.create_utility_node('condition', operation=4, firstTerm=attr_inn_bank, secondTerm=0, colorIfTrueR=attr_inn_bank, colorIfFalseR=0.0).outColorR # Less pymel.connectAttr(attr_roll_m, self.pivot_toes_ankle.rotateX) pymel.connectAttr(attr_roll_f, self.pivot_foot_front.rotateX) pymel.connectAttr(attr_roll_b, self.pivot_foot_back.rotateX) pymel.connectAttr(attr_bank_inn, self.pivot_foot_inn.rotateZ) pymel.connectAttr(attr_bank_out, self.pivot_foot_out.rotateZ) pymel.connectAttr(attr_inn_heel_roty, self.pivot_foot_heel.rotateY) pymel.connectAttr(attr_inn_front_roty, self.pivot_foot_front.rotateY) pymel.connectAttr(attr_inn_back_roty, self.pivot_foot_back.rotateY) pymel.connectAttr(attr_inn_ankle_rotz, self.pivot_toes_heel.rotateZ) pymel.connectAttr(attr_inn_toes_roty, self.pivot_foot_ankle.rotateY) pymel.connectAttr(attr_inn_toes_fk_rotx, self.pivot_foot_toes_fk.rotateX) # Create ikHandles and parent them # Note that we are directly parenting them so the 'Preserve Child Transform' of the translate tool still work. if jnt_heel: ikHandle_foot, ikEffector_foot = pymel.ikHandle(startJoint=jnt_foot, endEffector=jnt_heel, solver='ikSCsolver') else: ikHandle_foot, ikEffector_foot = pymel.ikHandle(startJoint=jnt_foot, endEffector=jnt_toes, solver='ikSCsolver') ikHandle_foot.rename(nomenclature_rig.resolve('ikHandle', 'foot')) ikHandle_foot.setParent(self.grp_rig) ikHandle_foot.setParent(self.pivot_toes_heel) if jnt_heel: ikHandle_heel, ikEffector_foot = pymel.ikHandle(startJoint=jnt_heel, endEffector=jnt_toes, solver='ikSCsolver') ikHandle_heel.rename(nomenclature_rig.resolve('ikHandle', 'heel')) ikHandle_heel.setParent(self.grp_rig) ikHandle_heel.setParent(self.pivot_foot_front) ikHandle_toes, ikEffector_toes = pymel.ikHandle(startJoint=jnt_toes, endEffector=jnt_tip, solver='ikSCsolver') ikHandle_toes.rename(nomenclature_rig.resolve('ikHandle', 'toes')) ikHandle_toes.setParent(self.grp_rig) ikHandle_toes.setParent(self.pivot_foot_toes_fk) # Hack: Re-constraint foot ikhandle # todo: cleaner! pymel.parentConstraint(self.ctrl_ik, root_footRoll, maintainOffset=True) # Connect the footroll to the main ikHandle # Note that we also need to hijack the softik network. fn_can_delete = lambda x: isinstance(x, pymel.nodetypes.Constraint) and \ not isinstance(x, pymel.nodetypes.PoleVectorConstraint) pymel.delete(filter(fn_can_delete, self._ik_handle_target.getChildren())) if jnt_heel: pymel.parentConstraint(self.pivot_toes_heel, self._ik_handle_target, maintainOffset=True) else: pymel.parentConstraint(self.pivot_toes_ankle, self._ik_handle_target, maintainOffset=True) ''' # Constraint swivel to ctrl_ik pymel.parentConstraint(self.ctrl_ik, self.ctrl_swivel, maintainOffset=True) # TODO: Implement SpaceSwitch ''' # Handle globalScale pymel.connectAttr(self.grp_rig.globalScale, root_footRoll.scaleX) pymel.connectAttr(self.grp_rig.globalScale, root_footRoll.scaleY) pymel.connectAttr(self.grp_rig.globalScale, root_footRoll.scaleZ)
def build_stack(self, stack, mult_u=1.0, mult_v=1.0): """ The dag stack is a chain of transform nodes daisy chained together that computer the final transformation of the influence. The decision of using transforms instead of multMatrix nodes is for clarity. Note also that because of it's parent (the offset node) the stack relative to the influence original translation. """ # TODO: Maybe use sub-classing to differenciate when we need to use a surface or not. nomenclature_rig = self.get_nomenclature_rig() # # Extract the base U and V of the base influence using the stack parent. (the 'offset' node) # surface_shape = self.surface.getShape() util_get_base_uv_absolute = libRigging.create_utility_node( 'closestPointOnSurface', inPosition=self._grp_offset.t, inputSurface=surface_shape.worldSpace) util_get_base_uv_normalized = libRigging.create_utility_node( 'setRange', oldMinX=surface_shape.minValueU, oldMaxX=surface_shape.maxValueU, oldMinY=surface_shape.minValueV, oldMaxY=surface_shape.maxValueV, minX=0, maxX=1, minY=0, maxY=1, valueX=util_get_base_uv_absolute.parameterU, valueY=util_get_base_uv_absolute.parameterV) attr_base_u_normalized = util_get_base_uv_normalized.outValueX attr_base_v_normalized = util_get_base_uv_normalized.outValueY self._attr_u_base = libAttr.addAttr( self.grp_rig, longName=self._ATTR_NAME_U_BASE, defaultValue=attr_base_u_normalized.get()) self._attr_v_base = libAttr.addAttr( self.grp_rig, longName=self._ATTR_NAME_V_BASE, defaultValue=attr_base_v_normalized.get()) pymel.connectAttr(attr_base_u_normalized, self.grp_rig.attr(self._ATTR_NAME_U_BASE)) pymel.connectAttr(attr_base_v_normalized, self.grp_rig.attr(self._ATTR_NAME_V_BASE)) # # Create follicle setup # The setup is composed of two follicles. # One for the "bind pose" and one "driven" by the avars.. # The delta between the "bind pose" and the "driven" follicles is then applied to the influence. # # Determine the follicle U and V on the reference nurbsSurface. # jnt_pos = self.jnt.getTranslation(space='world') # fol_pos, fol_u, fol_v = libRigging.get_closest_point_on_surface(self.surface, jnt_pos) base_u_val = self._attr_u_base.get() base_v_val = self._attr_v_base.get() # Resolve the length of each axis of the surface self._attr_length_u, self._attr_length_v, arcdimension_shape = libRigging.create_arclengthdimension_for_nurbsplane( self.surface) arcdimension_transform = arcdimension_shape.getParent() arcdimension_transform.rename(nomenclature_rig.resolve('arcdimension')) arcdimension_transform.setParent(self.grp_rig) # # Create two follicle. # - influenceFollicle: Affected by the ud and lr Avar # - bindPoseFollicle: A follicle that stay in place and keep track of the original position. # We'll then compute the delta of the position of the two follicles. # This allow us to move or resize the plane without affecting the built rig. (if the rig is in neutral pose) # offset_name = nomenclature_rig.resolve('bindPoseRef') obj_offset = pymel.createNode('transform', name=offset_name) obj_offset.setParent(self._grp_offset) fol_offset_name = nomenclature_rig.resolve('bindPoseFollicle') # fol_offset = libRigging.create_follicle(obj_offset, self.surface, name=fol_offset_name) fol_offset_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val) fol_offset = fol_offset_shape.getParent() fol_offset.rename(fol_offset_name) pymel.parentConstraint(fol_offset, obj_offset, maintainOffset=False) fol_offset.setParent(self.grp_rig) # Create the influence follicle influence_name = nomenclature_rig.resolve('influenceRef') influence = pymel.createNode('transform', name=influence_name) influence.setParent(self._grp_offset) fol_influence_name = nomenclature_rig.resolve('influenceFollicle') fol_influence_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val) fol_influence = fol_influence_shape.getParent() fol_influence.rename(fol_influence_name) pymel.parentConstraint(fol_influence, influence, maintainOffset=False) fol_influence.setParent(self.grp_rig) # # Extract the delta of the influence follicle and it's initial pose follicle # attr_localTM = libRigging.create_utility_node( 'multMatrix', matrixIn=[influence.worldMatrix, obj_offset.worldInverseMatrix]).matrixSum # Since we are extracting the delta between the influence and the bindpose matrix, the rotation of the surface # is not taken in consideration wich make things less intuitive for the rigger. # So we'll add an adjustement matrix so the rotation of the surface is taken in consideration. util_decomposeTM_bindPose = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=obj_offset.worldMatrix) attr_translateTM = libRigging.create_utility_node( 'composeMatrix', inputTranslate=util_decomposeTM_bindPose.outputTranslate ).outputMatrix attr_translateTM_inv = libRigging.create_utility_node( 'inverseMatrix', inputMatrix=attr_translateTM, ).outputMatrix attr_rotateTM = libRigging.create_utility_node( 'multMatrix', matrixIn=[obj_offset.worldMatrix, attr_translateTM_inv]).matrixSum attr_rotateTM_inv = libRigging.create_utility_node( 'inverseMatrix', inputMatrix=attr_rotateTM).outputMatrix attr_finalTM = libRigging.create_utility_node( 'multMatrix', matrixIn=[attr_rotateTM_inv, attr_localTM, attr_rotateTM]).matrixSum util_decomposeTM = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_finalTM) # # Resolve the parameterU and parameterV # self._attr_u_mult_inn = libAttr.addAttr( self.grp_rig, longName=self._ATTR_NAME_U_MULT, defaultValue=self._AVAR_DEFAULT_MULTIPLIER_U) self._attr_v_mult_inn = libAttr.addAttr( self.grp_rig, longName=self._ATTR_NAME_V_MULT, defaultValue=self._AVAR_DEFAULT_MULTIPLIER_V) attr_u_inn, attr_v_inn = self._get_follicle_absolute_uv_attr() # # Create the 1st (follicleLayer) that will contain the extracted position from the ud and lr Avar. # layer_follicle = stack.append_layer('follicleLayer') pymel.connectAttr(util_decomposeTM.outputTranslate, layer_follicle.translate) pymel.connectAttr(attr_u_inn, fol_influence.parameterU) pymel.connectAttr(attr_v_inn, fol_influence.parameterV) pymel.connectAttr(self._attr_u_base, fol_offset.parameterU) pymel.connectAttr(self._attr_v_base, fol_offset.parameterV) # # The second layer (oobLayer for out-of-bound) that allow the follicle to go outside it's original plane. # If the UD value is out the nurbsPlane UV range (0-1), ie 1.1, we'll want to still offset the follicle. # For that we'll compute a delta between a small increment (0.99 and 1.0) and multiply it. # nomenclature_rig = self.get_nomenclature_rig() oob_step_size = 0.001 # TODO: Expose a Maya attribute? fol_clamped_v_name = nomenclature_rig.resolve('influenceClampedV') fol_clamped_v_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val) fol_clamped_v = fol_clamped_v_shape.getParent() fol_clamped_v.rename(fol_clamped_v_name) fol_clamped_v.setParent(self.grp_rig) fol_clamped_u_name = nomenclature_rig.resolve('influenceClampedU') fol_clamped_u_shape = libRigging.create_follicle2(self.surface, u=base_u_val, v=base_v_val) fol_clamped_u = fol_clamped_u_shape.getParent() fol_clamped_u.rename(fol_clamped_u_name) fol_clamped_u.setParent(self.grp_rig) # Clamp the values so they never fully reach 0 or 1 for U and V. util_clamp_uv = libRigging.create_utility_node( 'clamp', inputR=attr_u_inn, inputG=attr_v_inn, minR=oob_step_size, minG=oob_step_size, maxR=1.0 - oob_step_size, maxG=1.0 - oob_step_size) clamped_u = util_clamp_uv.outputR clamped_v = util_clamp_uv.outputG pymel.connectAttr(clamped_v, fol_clamped_v.parameterV) pymel.connectAttr(attr_u_inn, fol_clamped_v.parameterU) pymel.connectAttr(attr_v_inn, fol_clamped_u.parameterV) pymel.connectAttr(clamped_u, fol_clamped_u.parameterU) # Compute the direction to add for U and V if we are out-of-bound. dir_oob_u = libRigging.create_utility_node( 'plusMinusAverage', operation=2, input3D=[fol_influence.translate, fol_clamped_u.translate]).output3D dir_oob_v = libRigging.create_utility_node( 'plusMinusAverage', operation=2, input3D=[fol_influence.translate, fol_clamped_v.translate]).output3D # Compute the offset to add for U and V condition_oob_u_neg = libRigging.create_utility_node( 'condition', operation=4, # less than firstTerm=attr_u_inn, secondTerm=0.0, colorIfTrueR=1.0, colorIfFalseR=0.0, ).outColorR condition_oob_u_pos = libRigging.create_utility_node( 'condition', # greater than operation=2, firstTerm=attr_u_inn, secondTerm=1.0, colorIfTrueR=1.0, colorIfFalseR=0.0, ).outColorR condition_oob_v_neg = libRigging.create_utility_node( 'condition', operation=4, # less than firstTerm=attr_v_inn, secondTerm=0.0, colorIfTrueR=1.0, colorIfFalseR=0.0, ).outColorR condition_oob_v_pos = libRigging.create_utility_node( 'condition', # greater than operation=2, firstTerm=attr_v_inn, secondTerm=1.0, colorIfTrueR=1.0, colorIfFalseR=0.0, ).outColorR # Compute the amount of oob oob_val_u_pos = libRigging.create_utility_node( 'plusMinusAverage', operation=2, input1D=[attr_u_inn, 1.0]).output1D oob_val_u_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_u_inn, input2X=-1.0).outputX oob_val_v_pos = libRigging.create_utility_node( 'plusMinusAverage', operation=2, input1D=[attr_v_inn, 1.0]).output1D oob_val_v_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_v_inn, input2X=-1.0).outputX oob_val_u = libRigging.create_utility_node( 'condition', operation=0, firstTerm=condition_oob_u_pos, secondTerm=1.0, colorIfTrueR=oob_val_u_pos, colorIfFalseR=oob_val_u_neg).outColorR oob_val_v = libRigging.create_utility_node( 'condition', operation=0, firstTerm=condition_oob_v_pos, secondTerm=1.0, colorIfTrueR=oob_val_v_pos, colorIfFalseR=oob_val_v_neg).outColorR oob_amount_u = libRigging.create_utility_node( 'multiplyDivide', operation=2, input1X=oob_val_u, input2X=oob_step_size).outputX oob_amount_v = libRigging.create_utility_node( 'multiplyDivide', operation=2, input1X=oob_val_v, input2X=oob_step_size).outputX oob_offset_u = libRigging.create_utility_node('multiplyDivide', input1X=oob_amount_u, input1Y=oob_amount_u, input1Z=oob_amount_u, input2=dir_oob_u).output oob_offset_v = libRigging.create_utility_node('multiplyDivide', input1X=oob_amount_v, input1Y=oob_amount_v, input1Z=oob_amount_v, input2=dir_oob_v).output # Add the U out-of-bound-offset only if the U is between 0.0 and 1.0 oob_u_condition_1 = condition_oob_u_neg oob_u_condition_2 = condition_oob_u_pos oob_u_condition_added = libRigging.create_utility_node( 'addDoubleLinear', input1=oob_u_condition_1, input2=oob_u_condition_2).output oob_u_condition_out = libRigging.create_utility_node( 'condition', operation=0, # equal firstTerm=oob_u_condition_added, secondTerm=1.0, colorIfTrue=oob_offset_u, colorIfFalse=[0, 0, 0]).outColor # Add the V out-of-bound-offset only if the V is between 0.0 and 1.0 oob_v_condition_1 = condition_oob_v_neg oob_v_condition_2 = condition_oob_v_pos oob_v_condition_added = libRigging.create_utility_node( 'addDoubleLinear', input1=oob_v_condition_1, input2=oob_v_condition_2).output oob_v_condition_out = libRigging.create_utility_node( 'condition', operation=0, # equal firstTerm=oob_v_condition_added, secondTerm=1.0, colorIfTrue=oob_offset_v, colorIfFalse=[0, 0, 0]).outColor oob_offset = libRigging.create_utility_node( 'plusMinusAverage', input3D=[oob_u_condition_out, oob_v_condition_out]).output3D layer_oob = stack.append_layer('oobLayer') pymel.connectAttr(oob_offset, layer_oob.t) # # Create the third layer that apply the translation provided by the fb Avar. # layer_fb = stack.append_layer('fbLayer') attr_get_fb = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_fb, input2X=self._attr_length_u).outputX attr_get_fb_adjusted = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_get_fb, input2X=0.1).outputX pymel.connectAttr(attr_get_fb_adjusted, layer_fb.translateZ) # # Create the 4th layer (folRot) that apply the rotation provided by the follicle controlled by the ud and lr Avar. # This is necessary since we don't want to rotation to affect the oobLayer and fbLayer. # layer_follicle_rot = stack.append_layer('folRot') pymel.connectAttr(util_decomposeTM.outputRotate, layer_follicle_rot.rotate) # # Create a 5th layer that apply the yw, pt, rl and Avar. # layer_rot = stack.append_layer('rotLayer') pymel.connectAttr(self.attr_yw, layer_rot.rotateY) pymel.connectAttr(self.attr_pt, layer_rot.rotateX) pymel.connectAttr(self.attr_rl, layer_rot.rotateZ) return stack