foreword
The principle has been mentioned in this document before , it is purely a self-promotion product, probably only you can understand what you wrote. Then implement it according to this idea. The main purpose of this article is to review the node editor. The feel of the maya node editor is really messy.
- This series is dug from a foreign tutorial, it is not suitable for colored pens like me, the operation is really fast.
Code
I still think the code is the clearest, put the code first
def add_ik_stretch(side, part, ik_chain, base_ctrl, local_ctrl, world_ctrl,primary_axis):
base_name = side +'_' + part
# start to end distance
limb_dist = cmds.createNode('distanceBetween', name=base_name+'_DIST')
# cinditon node
limb_cnd = cmds.createNode('condition', name=base_name+'CND')
# allocate locater, get transform node
start_LOC = cmds.spaceLocator(name=base_name+'_start_LOC')[0]
end_LOC = cmds.spaceLocator(name=base_name+'_end_LOC')[0]
stretch_mdn = cmds.createNode('multiplyDivide', name=base_name+'_stretch_MDN')
# calculate section length
length_a = distance_between(ik_chain[0],ik_chain[1])
length_b = distance_between(ik_chain[1], ik_chain[2])
total_length = length_b + length_a
# measure start-end desired direct distance
# move the loc first
cmds.pointConstraint(base_ctrl, start_LOC, maintainOffset=False)
cmds.pointConstraint(local_ctrl, end_LOC, maintainOffset=False)
connectAttr(start_LOC, 'worldMatrix[0]', limb_dist, 'inMatrix1')
connectAttr(end_LOC, 'worldMatrix[0]', limb_dist, 'inMatrix2')
# length ratio
connectAttr(limb_dist, 'distance', stretch_mdn,'input1X')
cmds.setAttr(stretch_mdn+'.input2X', total_length)#set devision
cmds.setAttr(stretch_mdn+'.operation', 2)
connectAttr(limb_dist,'distance',limb_cnd,'firstTerm')
connectAttr(stretch_mdn,'outputX', limb_cnd,'colorIfTrueR')
cmds.setAttr(limb_cnd+'.secondTerm', total_length)
cmds.setAttr(limb_cnd+'.operation',3)
# switch stretch
cmds.addAtter(world_ctrl, attributeType='double', min=0, max=1,
defaultValue=1, keyable=True, longName='stretch')
up_name = 'up' + part.title()
lo_name = 'lo' + part.title()
cmds.addAtter(world_ctrl, attributeType='double',
defaultValue=1, keyable=True, longName='up_name')
cmds.addAtter(world_ctrl, attributeType='double',
defaultValue=1, keyable=True, longName='lo_name')
stretch_bta = cmds.createNode('blendTwoAttr', name = base_name+'_BTA')
cmds.setAttr(stretch_bta+'.input[0]', 1)
connectAttr(limb_cnd,'outColorR',stretch_bta,'input[1]')
connectAttr(world_ctrl,'stretch',stretch_bta,'attributesBlender')
up_pma = cmds.createNode('plusMinusAverage', name=up_name+'_PMA')
lo_pma = cmds.createNode('plusMinusAverage', name=lo_name + '_PMA')
connectAttr(world_ctrl, up_name, up_pma, 'input1D[0]')
connectAttr(world_ctrl, lo_name, lo_pma, 'input1D[0]')
connectAttr(stretch_bta, 'output', up_pma, 'input1D[1]')
connectAttr(stretch_bta, 'output', lo_pma, 'input1D[1]')
cmds.setAttr(up_pma+'.input1D[2]', -1)
cmds.setAttr(lo_pma + '.input1D[2]', -1)
connectAttr(up_pma,'output1D',
ik_chain[0],'scale'+primary_axis[-1])
connectAttr(lo_pma,'output1D',
ik_chain[1],'scale'+primary_axis[-1])
return_dict = {
'measure_locs': [start_LOC, end_LOC],
'total_length': total_length,
'mdn': stretch_mdn,
'cnd': limb_cnd}
return return_dict
process explanation
Get effector transformation information
We first create two locators and move the locators to the positions of the start and end effectors.
The position of start_effector is recorded by base_ctrl(start_effector) in advance.
Similarly, end_effector is recorded by local_ctrl. The function of this local_ctrl is to control the hand, but there is no hand joint added here.
- 移动locator到effectors
cmds.pointConstraint(base_ctrl, start_LOC, maintainOffset=False)
cmds.pointConstraint(local_ctrl, end_LOC, maintainOffset=False)
Calculate some distance values
- Get the distance between the effectors, the distance of the effector is the target we expect to lengthen the original IKhandeler, this number is more critical
limb_dist.distance
# start to end distance
limb_dist = cmds.createNode('distanceBetween', name=base_name+'_DIST')
···
connectAttr(start_LOC, 'worldMatrix[0]', limb_dist, 'inMatrix1')
connectAttr(end_LOC, 'worldMatrix[0]', limb_dist, 'inMatrix2')
-
Model the distances of the upper and lower arms and their sum
total_length
length_a = distance_between(ik_chain[0],ik_chain[1]) length_b = distance_between(ik_chain[1], ik_chain[2]) total_length = length_b + length_a
Get the stretch ratio
Tips: The following function connectAttr()
is the cmds method rewritten by myself, and the functions are the same
stretch_mdn = cmds.createNode('multiplyDivide', name=base_name+'_stretch_MDN')
···
# length ratio
connectAttr(limb_dist, 'distance', stretch_mdn,'input1X')
cmds.setAttr(stretch_mdn+'.input2X', total_length)#set devision
cmds.setAttr(stretch_mdn+'.operation', 2)
Yes stretch ratio = effectors的距离/模型手臂长
, this part is completed with the node 'multiplyDivide'. It is very troublesome, if you want to achieve real-time calculation, you must use nodes, unless it is a constant value, such as the constant of the model arm length, we can calculate it offline
Conditional branch switching of stretch ratio
# cinditon node
limb_cnd = cmds.createNode('condition', name=base_name+'CND')
···
connectAttr(limb_dist,'distance',limb_cnd,'firstTerm')
connectAttr(stretch_mdn,'outputX', limb_cnd,'colorIfTrueR')
cmds.setAttr(limb_cnd+'.secondTerm', total_length)
cmds.setAttr(limb_cnd+'.operation',3)
The reason is: effectors距离>=模型手臂总长
when it is enabled, it is calculated before strech ratio
, otherwise it is 1
Add a few attributes to the end effctor
The new attribute we add here is on the parent controller world_ctrl of local_ctrl, and the pivot world positions of these two things are the same
# switch stretch
cmds.addAtter(world_ctrl, attributeType='double', min=0, max=1,
defaultValue=1, keyable=True, longName='stretch')
up_name = 'up' + part.title()
lo_name = 'lo' + part.title()
cmds.addAtter(world_ctrl, attributeType='double',
defaultValue=1, keyable=True, longName='up_name')
cmds.addAtter(world_ctrl, attributeType='double',
defaultValue=1, keyable=True, longName='lo_name')
- Stretch: the mixing ratio of IK overall stretching or not stretching
- up Arm: The stretch factor of the upper arm
- lo Arm: the same for the lower arm
Achieving Joint Stretch
stretch_bta = cmds.createNode('blendTwoAttr', name = base_name+'_BTA')
cmds.setAttr(stretch_bta+'.input[0]', 1)
connectAttr(limb_cnd,'outColorR',stretch_bta,'input[1]')
connectAttr(world_ctrl,'stretch',stretch_bta,'attributesBlender')
- One pitfall: Under the pure node editor, the input of the BTA node must be activated, and the constant cannot be entered if it is not activated.
input[0]=1
On behalf of the world_ctrlde stretch coefficient is 0, take the same measures
-
Obtain the stretch of the part and realize the scaling of the upper and lower skeletons
up_pma = cmds.createNode('plusMinusAverage', name=up_name+'_PMA')
lo_pma = cmds.createNode('plusMinusAverage', name=lo_name + '_PMA')
connectAttr(world_ctrl, up_name, up_pma, 'input1D[0]')
connectAttr(world_ctrl, lo_name, lo_pma, 'input1D[0]')
connectAttr(stretch_bta, 'output', up_pma, 'input1D[1]')
connectAttr(stretch_bta, 'output', lo_pma, 'input1D[1]')
cmds.setAttr(up_pma+'.input1D[2]', -1)
cmds.setAttr(lo_pma + '.input1D[2]', -1)
We use the stretch ratio that has been attenuated by the scaling factor of the upper and lower arms according to the above figure for the scale of the IK joint to achieve stretching
connectAttr(up_pma,'output1D',
ik_chain[0],'scale'+primary_axis[-1])
connectAttr(lo_pma,'output1D',
ik_chain[1],'scale'+primary_axis[-1])
end
The above is the general sorting process and there are still many details left unsaid. I am afraid that I will forget it. I also put the mb file with the same function in the node editor version , which can only be opened after 2020.