def __create_pivots(self, ik_control, pivots): """ """ hierarchy = { "ball_pivot": { "heel_pivot_ctrl": { "out_pivot": { "in_pivot": { "toe_pivot_ctrl": { "toe_ctrl": None, "heel_ctrl": None } } } } } } hierarchy = common.RigHierarchy( hierarchy, prefix="{}_".format(self.name), suffix="", lock_and_hide=["s", "v"], ) hierarchy.create() cmds.parent(hierarchy.ball_pivot, ik_control) for driver, driven in [ [self.ball_joint, hierarchy.ball_pivot], [self.two_bone_ik.end_joint, hierarchy.heel_pivot_ctrl], [self.ball_joint, hierarchy.out_pivot], [self.ball_joint, hierarchy.in_pivot], [self.toe_joint, hierarchy.toe_pivot_ctrl], [self.ball_joint, hierarchy.toe_ctrl], [self.ball_joint, hierarchy.heel_ctrl], ]: if driver and driven: common.snap_to_position(driven, driver) for node, position in pivots.items(): node = getattr(hierarchy, node, None) if not node: continue children = cmds.listRelatives(node, children=True, path=True) if children: cmds.parent(children, world=True) cmds.xform(node, ws=True, t=position) if children: cmds.parent(children, node) hierarchy.parent_to_heel_ctrl(self.ik_handle_ball) if self.ik_handle_toe: hierarchy.parent_to_toe_ctrl(self.ik_handle_toe) for node in hierarchy: common.lock_and_hide(node, "t") cmds.setAttr("{}.rotateOrder".format(hierarchy.heel_ctrl), 2) # zxy self.hierarchy = hierarchy
def __create_ik(self, ik_control, pole_vector, soft_ik_parent, global_scale_attr, scale_stretch): self.ik_handle = cmds.ikHandle( name="{}_ikh".format(self.name), solver="ikRPsolver", startJoint=self.start_joint, endEffector=self.end_joint, )[0] cmds.setAttr("{}.v".format(self.ik_handle), 0) # Drive visibility ik_vis = dge("1.0 - ikFk", ikFk="{}.ikFk".format(self.config_control)) for node in [ik_control, pole_vector]: vis = "{}.v".format(node) locked = cmds.getAttr(vis, lock=True) cmds.setAttr(vis, lock=False) cmds.connectAttr(ik_vis, "{}.v".format(node)) if locked: cmds.setAttr(vis, lock=True) dge( "ikBlend = 1.0 - ikFk", ikBlend="{}.ikBlend".format(self.ik_handle), ikFk="{}.ikFk".format(self.config_control), ) self.soft_ik = cmds.createNode("transform", name="{}_soft_ik".format(self.name)) common.snap_to_position(self.soft_ik, self.end_joint) cmds.parent(self.ik_handle, self.soft_ik) cmds.parent(self.soft_ik, soft_ik_parent) self.__create_stretch(ik_control, global_scale_attr, scale_stretch) cmds.parent(self.end_loc, soft_ik_parent) cmds.poleVectorConstraint(pole_vector, self.ik_handle)
def __create_stretch(self, ik_control, global_scale_attr=None, scale_stretch=True): """Create the stretchy soft ik setup. :param ik_control: Name of the node to use as the ik control. :param global_scale_attr: Optional attribute containing global scale value. :param scale_stretch: True to stretch with scale, False to use translate. """ for attr in ["stretch", "softIk"]: cmds.addAttr( ik_control, ln=attr, minValue=0.0, maxValue=1.0, defaultValue=0.0, keyable=True, ) # Locator for start distance measurement self.start_loc = cmds.spaceLocator( name="{}_stretch_start".format(self.name))[0] parent = cmds.listRelatives(self.start_joint, parent=True, path=True) if parent: cmds.connectAttr( "{}.worldMatrix[0]".format(parent[0]), "{}.offsetParentMatrix".format(self.start_loc), ) common.snap_to_position(self.start_loc, self.start_joint) # Locator for end distance measurement self.end_loc = cmds.spaceLocator( name="{}_stretch_end".format(self.name))[0] cmds.setAttr("{}.v".format(self.end_loc), 0) common.snap_to_position(self.end_loc, self.end_joint) rest_length = shortcuts.distance(self.start_joint, self.mid_joint) rest_length += shortcuts.distance(self.mid_joint, self.end_joint) length_ratio = dge( "distance(start, end) / (restLength * globalScale)", container="{}_percent_from_rest".format(self.name), start=self.start_loc, end=self.end_loc, restLength=rest_length, globalScale=global_scale_attr or 1.0, ) # Prevent divide by 0 softik = dge("max(x, 0.001)", x="{}.softIk".format(ik_control)) # We need to adjust offset the ik handle and scale the joints to create the soft # effect # See this graph to see the the softIk and scale values plotted # https://www.desmos.com/calculator/csi40rsztl # x = length_ratio # f(x) = softik_scale # c(x) = Scale x of the joints # s = softIk attribute # t = stretch attribute softik_scale = dge( "x > (1.0 - softIk)" "? (1.0 - softIk) + softIk * (1.0 - exp(-(x - (1.0 - softIk)) / softIk)) " ": x", container="{}_softik".format(self.name), x=length_ratio, softIk=softik, ) compose_matrix = cmds.createNode("composeMatrix") # Set the effector position dge( "tx = restLength * lerp(softIk, lengthRatio, stretch)", container="{}_effector_position".format(self.name), tx="{}.inputTranslate.inputTranslateX".format(compose_matrix), restLength=rest_length, lengthRatio=length_ratio, softIk=softik_scale, stretch="{}.stretch".format(ik_control), ) # Drive the joint scale for stretch scale = dge( "lerp(1, lengthRatio / softIk, stretch)", container="{}_stretch_scale".format(self.name), lengthRatio=length_ratio, softIk=softik_scale, stretch="{}.stretch".format(ik_control), ) if scale_stretch: for node in [self.start_joint, self.mid_joint]: cmds.connectAttr(scale, "{}.sx".format(node)) inverse_scale = dge("1/sx", sx="{}.sx".format(node)) cmds.connectAttr(inverse_scale, "{}.sy".format(node)) cmds.connectAttr(inverse_scale, "{}.sz".format(node)) else: for node in [self.mid_joint, self.end_joint]: tx = cmds.getAttr("{}.tx".format(node)) dge("x = {} * s".format(tx), x="{}.tx".format(node), s=scale) # Drive the soft ik transform aim = cmds.createNode("aimMatrix") cmds.connectAttr("{}.worldMatrix[0]".format(self.start_loc), "{}.inputMatrix".format(aim)) cmds.connectAttr( "{}.worldMatrix[0]".format(self.end_loc), "{}.primary.primaryTargetMatrix".format(aim), ) mult = cmds.createNode("multMatrix") cmds.connectAttr("{}.outputMatrix".format(compose_matrix), "{}.matrixIn[0]".format(mult)) cmds.connectAttr("{}.outputMatrix".format(aim), "{}.matrixIn[1]".format(mult)) parent = cmds.listRelatives(self.soft_ik, parent=True, path=True)[0] if parent: cmds.connectAttr("{}.worldInverseMatrix[0]".format(parent), "{}.matrixIn[2]".format(mult)) pick = cmds.createNode("pickMatrix") cmds.connectAttr("{}.matrixSum".format(mult), "{}.inputMatrix".format(pick)) for attr in ["Scale", "Shear", "Rotate"]: cmds.setAttr("{}.use{}".format(pick, attr), 0) cmds.connectAttr("{}.outputMatrix".format(pick), "{}.offsetParentMatrix".format(self.soft_ik)) cmds.setAttr("{}.t".format(self.soft_ik), 0, 0, 0)
def __create_pivots(self, ik_control, pivots): """ """ hierarchy = { "soft_ik": { "ball_pivot": { "heel_pivot": { "out_pivot": { "in_pivot": { "toe_pivot": { "toe_lift": None, "heel_raise": { "pole_vector_rotate": { "pole_vector": None } }, } } } } } } } hierarchy = common.RigHierarchy( hierarchy, prefix="{}_".format(self.name), suffix="", lock_and_hide=["s", "v"], ) hierarchy.create() cmds.parent(hierarchy.soft_ik, ik_control) for driver, driven in [ [self.ankle_joint, hierarchy.soft_ik], [self.ball_joint, hierarchy.ball_pivot], [self.ankle_joint, hierarchy.heel_pivot], [self.ball_joint, hierarchy.out_pivot], [self.ball_joint, hierarchy.in_pivot], [self.toe_joint, hierarchy.toe_pivot], [self.ball_joint, hierarchy.toe_lift], [self.ball_joint, hierarchy.heel_raise], [self.ankle_joint, hierarchy.pole_vector_rotate], ]: common.snap_to_position(driven, driver) for node, position in pivots.items(): node = getattr(hierarchy, node) if not node: continue children = cmds.listRelatives(node, children=True, path=True) if children: cmds.parent(children, world=True) cmds.xform(node, ws=True, t=position) if children: cmds.parent(children, node) hierarchy.parent_to_toe_lift(self.ik_handle_ball) hierarchy.parent_to_toe_lift(self.ik_handle_toe) hierarchy.parent_to_heel_raise(self.ik_handle_leg) cmds.poleVectorConstraint(hierarchy.pole_vector, self.ik_handle_leg) cmds.xform(hierarchy.pole_vector, ws=True, r=True, t=(50, 0, 0)) cmds.setAttr("{}.twist".format(self.ik_handle_leg), 90) self.hierarchy = hierarchy
def __create_stretch(self, ik_control, global_scale_attr=None): cmds.addAttr( ik_control, ln="stretch", minValue=0.0, maxValue=1.0, defaultValue=1.0, keyable=True, ) # Locator for start distance measurement self.start_loc = cmds.spaceLocator( name="{}_stretch_start".format(self.name))[0] common.snap_to_position(self.start_loc, self.up_leg_joint) parent = cmds.listRelatives(self.up_leg_joint, parent=True, path=True) if parent: cmds.parentConstraint(parent[0], self.start_loc, mo=True) cmds.scaleConstraint(parent[0], self.start_loc) start_loc = cmds.listRelatives(self.start_loc, children=True, shapes=True)[0] # Locator for end distance measurement self.end_loc = cmds.spaceLocator( name="{}_stretch_end".format(self.name))[0] cmds.setAttr("{}.v".format(self.end_loc), 0) common.snap_to_position(self.end_loc, self.ankle_joint) cmds.parent(self.end_loc, ik_control) end_loc = cmds.listRelatives(self.end_loc, children=True, shapes=True)[0] distance_between = cmds.createNode("distanceBetween", name="{}_distance".format( self.name)) cmds.connectAttr("{}.worldPosition".format(start_loc), "{}.point1".format(distance_between)) cmds.connectAttr("{}.worldPosition".format(end_loc), "{}.point2".format(distance_between)) mdn = cmds.createNode("multiplyDivide", name="{}_stretch_scale".format(self.name)) rest_length = shortcuts.distance(self.up_leg_joint, self.knee_joint) rest_length += shortcuts.distance(self.knee_joint, self.ankle_joint) cmds.setAttr("{}.operation".format(mdn), 2) # divide cmds.connectAttr("{}.distance".format(distance_between), "{}.input1X".format(mdn)) if global_scale_attr: global_scale = cmds.createNode("multDoubleLinear", name="{}_global_scale".format( self.name)) cmds.setAttr("{}.input1".format(global_scale), rest_length) cmds.connectAttr(global_scale_attr, "{}.input2".format(global_scale)) cmds.connectAttr("{}.output".format(global_scale), "{}.input2X".format(mdn)) else: cmds.setAttr("{}.input2X".format(mdn), rest_length) self.percent_rest_distance = "{}.outputX".format(mdn) softik_percentage = self.__create_soft_ik(ik_control) # Create the locators used to calculate the actual position we want the ik to # be placed loc = cmds.spaceLocator(name="{}_softik_aim".format(self.name))[0] offset_loc = cmds.spaceLocator( name="{}_softik_goal".format(self.name))[0] cmds.parent(offset_loc, loc) cmds.parent(loc, self.start_loc) common.snap_to_position(loc, self.start_loc) cmds.aimConstraint(self.end_loc, loc, worldUpType="none") # Calculate length for the target position to allow stretch with soft ik # rest_length = rest_length * min(1, percent_rest) * stretch) # min(1, percent_rest) clamp = cmds.createNode("clamp", name="{}_stretch_clamp".format(self.name)) cmds.setAttr("{}.minR".format(clamp), 1) cmds.setAttr("{}.maxR".format(clamp), 100) cmds.connectAttr(self.percent_rest_distance, "{}.inputR".format(clamp)) stretch_percent = "{}.outputR".format(clamp) # min(1, percent_rest) * stretch enable_stretch = cmds.createNode("blendTwoAttr", name="{}_stretch_enable".format( self.name)) cmds.setAttr("{}.input[0]".format(enable_stretch), 1) cmds.connectAttr(stretch_percent, "{}.input[1]".format(enable_stretch)) cmds.connectAttr( "{}.stretch".format(ik_control), "{}.attributesBlender".format(enable_stretch), ) stretch_factor = "{}.output".format(enable_stretch) # rest_length * min(1, percent_rest) * stretch mdl = cmds.createNode("multDoubleLinear", name="{}_stretch_target_length".format( self.name)) cmds.setAttr("{}.input1".format(mdl), rest_length) cmds.connectAttr(stretch_factor, "{}.input2".format(mdl)) target_rest_length = "{}.output".format(mdl) # Now the final position will be the percentage of the rest length calculated # from soft ik mdl = cmds.createNode("multDoubleLinear") cmds.connectAttr(softik_percentage, "{}.input1".format(mdl)) cmds.connectAttr(target_rest_length, "{}.input2".format(mdl)) cmds.connectAttr("{}.output".format(mdl), "{}.tx".format(offset_loc)) cmds.pointConstraint(offset_loc, self.hierarchy.soft_ik) # Inverse scale for volume preservation inverse_scale = cmds.createNode("multiplyDivide", name="{}_inverse_scale".format( self.name)) cmds.setAttr("{}.operation".format(inverse_scale), 2) # divide cmds.setAttr("{}.input1X".format(inverse_scale), 1) cmds.connectAttr(stretch_factor, "{}.input2X".format(inverse_scale)) for node in [self.up_leg_joint, self.knee_joint]: cmds.connectAttr(stretch_factor, "{}.sx".format(node)) cmds.connectAttr("{}.outputX".format(inverse_scale), "{}.sy".format(node)) cmds.connectAttr("{}.outputX".format(inverse_scale), "{}.sz".format(node))