def attach_to_plane(self, constraint_rot=True): """ Create follicle attached to the place for each input joint :param constraint_rot: Are the joints will be constraint in rotation on the follicle :return: Nothing """ nomenclature_rig = self.get_nomenclature_rig() fol_v = 0.5 # Always in the center #split_value = 1.0 / (len(self.chain_jnt) - 1) for i, jnt in enumerate(self.chain_jnt): #fol_u = split_value * i # TODO: Validate that we don't need to inverse the rotation separately. jnt_pos = jnt.getMatrix(worldSpace=True).translate pos, fol_u, fol_v = libRigging.get_closest_point_on_surface(self._ribbon_shape, jnt_pos) fol_name = nomenclature_rig.resolve("ribbonFollicle{0:02d}".format(i)) fol_shape = libRigging.create_follicle2(self._ribbon_shape, u=fol_u, v=fol_v) fol = fol_shape.getParent() fol.rename(fol_name) if constraint_rot: pymel.parentConstraint(fol, jnt, mo=True) else: pymel.pointConstraint(fol, jnt, mo=True) self._follicles.append(fol)
def _create_follicle(self, ctrl_tm, influence, obj_mesh=None, u_coord=None, v_coord=None): nomenclature_rig = self.get_nomenclature_rig() # Create a follicle, this will be used for callibration purpose. # If this affect performance we can create it only when necessary, however being able to # see it help with debugging. # Resolve u and v coordinates if obj_mesh is None: # We'll scan all available geometries and use the one with the shortest distance. meshes = libHistory.get_affected_shapes(influence) 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, ctrl_tm.translate) else: _, out_u, out_v = libRigging.get_closest_point_on_shape(obj_mesh, ctrl_tm.translate) # 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 fol_name = nomenclature_rig.resolve('follicle') fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord) fol_transform = fol_shape.getParent() fol_transform.rename(fol_name) fol_transform.setParent(self.grp_rig) return fol_transform, fol_shape
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, 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_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
def build(self, ctrl_size_max=None, ctrl_size_min=None, parent=True, **kwargs): """ :param ctrl_size_max: Used to automatically size layer ctrls. Define the maximum size (applied on first layer) :param ctrl_size_min: Used to automatically size layer ctrls. Define the minimum size (applied on last layer) :param parent: Redefined to compensate for bad design. Identical implementation than base class. :param kwargs: Any keyword argument will be forwarded to the base method. """ super(InteractiveFK, self).build(parent=None, **kwargs) nomenclature_rig_grp = self.get_nomenclature_rig_grp() nomenclature_jnt = self.get_nomenclature_jnt() # Create a group that we will parent all surfaces to. self._grp_surfaces = pymel.createNode( 'transform', name=nomenclature_rig_grp.resolve('surfaces'), parent=self.grp_rig ) self._init_layers() # Resolve default ctrl_size if ctrl_size_min is None or ctrl_size_max is None: val = self._get_default_ctrl_size() ctrl_size_max = val * 0.25 ctrl_size_min = ctrl_size_max / float(len(self.layers)) self.info('Default ctrl size is adjusted from bettwen {} at {}'.format(ctrl_size_min, ctrl_size_max)) self._build_layers( ctrl_size_max=ctrl_size_max, ctrl_size_min=ctrl_size_min ) # Create a group that represent the original parent of everything. # This allow use to supported non-uniform scaling by using direct connections instead of parent/scaleConstraint. parent_obj = self.get_parent_obj() self._grp_parent = pymel.createNode( 'transform', name=nomenclature_rig_grp.resolve('parent'), parent=self.grp_rig ) # Rig parenting if parent_obj: self._grp_parent.setMatrix(parent_obj.getMatrix(worldSpace=True)) # For each influence, create a follicle that will follow the final mesh. unassigned_influences = self._get_unassigned_influences() last_surface = self.layers[-1].get_surface() if unassigned_influences and last_surface: grp_follicles = pymel.createNode( 'transform', name=nomenclature_rig_grp.resolve('follicles'), parent=self.grp_rig, ) for i, jnt in enumerate(unassigned_influences): nomenclature = nomenclature_jnt + self.rig.nomenclature(jnt.stripNamespace().nodeName()) # Get the final LOCAL transformation of the influence. # If we have a parent, we'll want to convert it to WORLD transformation. pos = jnt.getTranslation(space='world') _, u, v = libRigging.get_closest_point_on_surface(last_surface, pos) fol_shape = libRigging.create_follicle2(last_surface, u, v, connect_transform=True) fol_transform = fol_shape.getParent() fol_transform.rename(nomenclature.resolve()) fol_transform.setParent(grp_follicles) # Connect the influence. # Note that we don't apply any scale constraining since we assume that all influence have # the same common parent that drive the scale. if parent_obj: # Use an extra object to match original influence transform. grp_output = pymel.createNode( 'transform', name=nomenclature_jnt.resolve('output{}'.format(i)), parent=self._grp_parent ) grp_output.setMatrix(jnt.getMatrix(worldSpace=True), worldSpace=True) pymel.parentConstraint(fol_transform, grp_output, maintainOffset=True) # Hack: Reset joint orient so our direct connection work... # todo: use compose matrix? if isinstance(jnt, pymel.nodetypes.Joint): jnt.jointOrientX.set(0.0) jnt.jointOrientY.set(0.0) jnt.jointOrientZ.set(0.0) libAttr.connect_transform_attrs(grp_output, jnt, sx=False, sy=False, sz=False) else: pymel.parentConstraint(fol_transform, jnt, maintainOffset=True) # Manually parent the module with support for scaling. if parent_obj and parent_obj != self.grp_anm: pymel.parentConstraint(parent_obj, self.grp_anm, maintainOffset=True) pymel.scaleConstraint(parent_obj, self.grp_anm, maintainOffset=True)
def build(self, module, create_follicle=True, pos=None, shape=None, shape_next=None, u_coord=None, v_coord=None, constraint=True, **kwargs): """ :param pos: The position to use when seeking where to create the follicle. Can be resolved automatically if the module have an influence. :param shape: The shape to create the follicle on. Can be resolved automatically if the module have an influence. :param u_coord: The U coordinate to use for the follicle. Can be resolved automatically if the module have an influence. :param v_coord: The V coordinate to use for the follicle. Can be resolved automatically if the module have an influence. :param constraint: If True, the ctrl will drive the influence via direct connect. :param kwargs: Any additional keyword argument will be passed to the parent method. """ super(InteractiveFKCtrlModel, self).build( module, parent=None, # We handle the parenting ourself! **kwargs ) nomenclature_rig = self.get_nomenclature_rig() # Resolve bind position. if pos is None: pos = self.get_bind_pos() # # Create the 'bind' node that will follow the follicle in translation and something else in rotation. # # Create the a group containing the local offset in case we have an hyerarchy to preserve. self._grp_offset = pymel.createNode( 'transform', name=nomenclature_rig.resolve('offset'), parent=self.grp_rig ) attr_offset_tm = self._grp_offset.matrix # Create a reference to the previous deformation self._grp_bind = pymel.createNode( 'transform', name=nomenclature_rig.resolve('follicle'), parent=self.grp_rig ) # self._layer_bind = self._stack.append_layer() # self._layer_bind.rename(layer_fol_name) self._grp_bind.setMatrix(self.get_bind_tm()) # self._layer_bind.setParent(self.grp_rig) attr_bind_tm = self._grp_bind.matrix attr_bind_tm_inv = self._grp_bind.inverseMatrix # Compute the parent offset and the deformation offset toguether. attr_total_offset = libRigging.create_utility_node( 'multMatrix', name=nomenclature_rig.resolve('getOffset'), matrixIn=( attr_offset_tm, attr_bind_tm, ) ).matrixSum # Create follicle to track the transform BEFORE the ctrl. if create_follicle: # Resolve mesh if necessary. if not shape: shape = self.get_default_shape() if not shape: raise Exception("Can't resolve mesh to attach to!") # Resolve uv coords if necessary if u_coord is None or v_coord is None: _, u_coord, v_coord = libRigging.get_closest_point_on_shape(shape, pos) if u_coord is None or v_coord is None: raise Exception("Can't resolve uv coordinates to use!") fol_shape = libRigging.create_follicle2(shape, u=u_coord, v=v_coord) ref_before = fol_shape.getParent() ref_before.rename(nomenclature_rig.resolve('preCtrl')) ref_before.setParent(self.grp_rig) else: ref_before = pymel.createNode( 'transform', name=nomenclature_rig.resolve('preCtrl'), parent=self.grp_rig ) ref_before.setMatrix(self.get_bind_tm()) pymel.parentConstraint(ref_before, self._grp_bind, maintainOffset=True) # Create follicle to track the transfort AFTER the ctrl. # This will be used to position the controller correctly. if shape_next: # Resolve uv coords if necessary _, u_coord, v_coord = libRigging.get_closest_point_on_shape(shape_next, pos) if u_coord is None or v_coord is None: raise Exception("Can't resolve uv coordinates to use!") fol_shape = libRigging.create_follicle2(shape_next, u=u_coord, v=v_coord) ref_after = fol_shape.getParent() ref_after.rename(nomenclature_rig.resolve('postCtrl')) ref_after.setParent(self.grp_rig) self.follicle = ref_after # # Constraint grp_anm # # Create an output object that will hold the world position of the ctrl offset. # This allow us to create direct connection which simplify the dag tree for the animator # and allow us to easily scale the whole setup to support non-uniform scaling. util_decompose_offset = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_total_offset ) pymel.connectAttr(util_decompose_offset.outputTranslate, self.ctrl.offset.translate) pymel.connectAttr(util_decompose_offset.outputRotate, self.ctrl.offset.rotate) # # Create an output group that contain the new joint position # grp_scale = pymel.createNode( 'transform', name=nomenclature_rig.resolve('parent'), parent=self.grp_rig ) self._grp_output = pymel.createNode( 'transform', name=nomenclature_rig.resolve('output'), parent=grp_scale ) attr_get_local_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=( self.ctrl.matrix, attr_total_offset ) ).matrixSum util_decompose_local_tm = libRigging.create_utility_node( 'decomposeMatrix', inputMatrix=attr_get_local_tm ) pymel.connectAttr(util_decompose_local_tm.outputTranslate, self._grp_output.translate) pymel.connectAttr(util_decompose_local_tm.outputRotate, self._grp_output.rotate) pymel.connectAttr(util_decompose_local_tm.outputScale, self._grp_output.scale) pymel.parentConstraint(self._grp_output, self.jnt, maintainOffset=True) pymel.scaleConstraint(self._grp_output, self.jnt, maintainOffset=True) surface = self.get_surface() skincluster = _get_immediate_skincluster(surface) index = libSkinning.get_skin_cluster_influence_objects(skincluster).index(self.jnt) pymel.connectAttr(attr_bind_tm_inv, skincluster.bindPreMatrix[index], force=True)
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
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_stack(self, rig, stack, mult_u=1.0, mult_v=1.0): """ The dag stack is a stock of dagnode that act as additive deformer to controler the final position of the drived joint. """ # TODO: Maybe use sub-classing to differenciate when we need to use a surface or not. nomenclature_rig = self.get_nomenclature_rig(rig) # # 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) fol_u, fol_v = self.get_base_uv() # Create and connect follicle-related parameters u_base = fol_u # fol_influence.parameterU.get() v_base = 0.5 # fol_influence.parameterV.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 the bind pose follicle offset_name = nomenclature_rig.resolve('bindPoseRef') obj_offset = pymel.createNode('transform', name=offset_name) obj_offset.setParent(stack._layers[0]) 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=fol_u, v=fol_v) 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(stack._layers[0]) fol_influence_name = nomenclature_rig.resolve('influenceFollicle') fol_influence_shape = libRigging.create_follicle2(self.surface, u=fol_u, v=fol_v) 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 ) layer_follicle = stack.add_layer('follicleLayer') pymel.connectAttr(util_decomposeTM.outputTranslate, layer_follicle.translate) pymel.connectAttr(util_decomposeTM.outputRotate, layer_follicle.rotate) self._attr_u_base = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U_BASE, defaultValue=u_base) self._attr_v_base = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V_BASE, defaultValue=v_base) 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) self._attr_u_mult_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_U_MULT, defaultValue=mult_u) self._attr_v_mult_inn = libAttr.addAttr(self.grp_rig, longName=self._ATTR_NAME_V_MULT, defaultValue=mult_v) # Connect UD to V attr_get_v_offset = libRigging.create_utility_node('multiplyDivide', input1X=self.attr_ud, input2X=0.5 ).outputX attr_get_v_multiplied = libRigging.create_utility_node('multiplyDivide', input1X=attr_get_v_offset, input2X=self._attr_v_mult_inn).outputX attr_v_cur = libRigging.create_utility_node('addDoubleLinear', input1=self._attr_v_base, input2=attr_get_v_multiplied ).output pymel.connectAttr(attr_v_cur, attr_v_inn) # Connect LR to U attr_get_u_offset = libRigging.create_utility_node('multiplyDivide', input1X=self.attr_lr, input2X=0.5 ).outputX attr_get_u_multiplied = libRigging.create_utility_node('multiplyDivide', input1X=attr_get_u_offset, input2X=self._attr_u_mult_inn).outputX attr_u_cur = libRigging.create_utility_node('addDoubleLinear', input1=self._attr_u_base, input2=attr_get_u_multiplied ).output pymel.connectAttr(attr_u_cur, attr_u_inn) 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 OOB layer (out-of-bound) allow the follicle to go outside it's original plane. # HACK: 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(rig) oob_step_size = 0.001 # TODO: Expose a Maya attribute? jnt_tm = self.jnt.getMatrix(worldSpace=True) ''' inf_clamped_v_name= nomenclature_rig.resolve('influenceClampedVRef') inf_clamped_v = pymel.createNode('transform', name=inf_clamped_v_name) inf_clamped_v.setParent(stack._layers[0]) inf_clamped_u_name= nomenclature_rig.resolve('influenceClampedURef') inf_clamped_u = pymel.createNode('transform', name=inf_clamped_u_name) inf_clamped_u.setParent(stack._layers[0]) ''' fol_clamped_v_name = nomenclature_rig.resolve('influenceClampedV') fol_clamped_v_shape = libRigging.create_follicle2(self.surface, u=fol_u, v=fol_v) 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=fol_u, v=fol_v) 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_cur, inputG=attr_v_cur, 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_cur, fol_clamped_v.parameterU) pymel.connectAttr(attr_v_cur, 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_cur, 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_cur, 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_cur, 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_cur, 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_cur, 1.0]).output1D oob_val_u_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_u_cur, input2X=-1.0).outputX oob_val_v_pos = libRigging.create_utility_node('plusMinusAverage', operation=2, input1D=[attr_v_cur, 1.0]).output1D oob_val_v_neg = libRigging.create_utility_node('multiplyDivide', input1X=attr_v_cur, 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.add_layer('oobLayer') pymel.connectAttr(oob_offset, layer_oob.t) # # Build Front/Back setup # layer_fb = stack.add_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 a layer before the ctrl to apply the YW, PT and RL avar. # nomenclature_rig = self.get_nomenclature_rig(rig) layer_rot = stack.add_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
def build(self, parent, ref, ref_tm=None, grp_rig=None, obj_mesh=None, **kwargs): # todo: Simplify the setup, too many nodes # Resolve geometry to attach to if obj_mesh is None: obj_mesh = parent.get_farest_affected_mesh(ref) if obj_mesh is None: raise Exception("Can't find mesh affected by {0}. Skipping doritos ctrl setup.") super(InteractiveCtrl, self).build(parent, **kwargs) #nomenclature_anm = self.get_nomenclature_anm(parent) nomenclature_rig = parent.nomenclature(suffix=parent.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) need_flip = ref_tm.translate.x < 0 # 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.setMatrix(ref_tm ) # Add sensibility attributes # The values will be computed when attach_ctrl will be called self.attr_sensitivity_tx = libAttr.addAttr(stack, longName=self._ATTR_NAME_SENSITIVITY_TX, defaultValue=1.0) self.attr_sensitivity_ty = libAttr.addAttr(stack, longName=self._ATTR_NAME_SENSITIVITY_TY, defaultValue=1.0) self.attr_sensitivity_tz = libAttr.addAttr(stack, longName=self._ATTR_NAME_SENSITIVITY_TZ, defaultValue=1.0) log.info('Creating doritos setup from {0} to {1}'.format(self.jnt, obj_mesh)) # Resolve the doritos location ''' if ctrl_tm is None: ctrl_tm = self.jnt.getMatrix(worldSpace=True) ''' # Find the closest point on the surface. pos_ref = ref_tm.translate # 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.add_layer() layer_fol.rename(layer_fol_name) #layer_fol.setParent(self.grp_rig) fol_pos, fol_u, fol_v = libRigging.get_closest_point_on_mesh(obj_mesh, pos_ref) # TODO: Validate that we don't need to inverse the rotation separately. fol_name = nomenclature_rig.resolve('doritosFollicle') fol_shape = libRigging.create_follicle2(obj_mesh, u=fol_u, v=fol_v) fol = fol_shape.getParent() fol.rename(fol_name) pymel.parentConstraint(fol, layer_fol, maintainOffset=True) fol = fol_shape.getParent() fol.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 = parent.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) # # 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 need_flip: 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 need_flip: 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 ''' if need_flip: attr_adjustement_sx_inn = libRigging.create_utility_node('multiplyDivide', input1X=attr_sensibility_lr_inv, input2X=-1).outputX else: attr_adjustement_sx_inn = attr_sensibility_lr_inv ''' 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) fol.setParent(grp_rig)