-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIKspline_Limb_Tool.py
411 lines (343 loc) · 17.1 KB
/
IKspline_Limb_Tool.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
'''----------------------------------------------------
IK Spline Limb Tool | v.12 global clean up
----------------------------------------------------'''
### Usage example to run in Maya
'''
from importlib import reload
import IKspline_Limb_Tool
reload( IKspline_Limb_Tool )
limb_type = cmds.confirmDialog( title = 'Limb Type', message = 'Select the limb type:', button = ['Arm', 'Leg'] )
# Example values for the number of skinning joints : Modify these values as needed
num_SKIN_jnts_up = 5
num_SKIN_jnts_low = 5
# Change the radius of the control joints
jnt_radius = 2
IKspline_Limb_Tool.create_bendy_limb( limb_type, num_SKIN_jnts_up, num_SKIN_jnts_low, jnt_radius )
'''
import maya.cmds as cmds
import maya.OpenMaya as om
### HELPERS
def float_range( start, stop, step ):
return [ start + i * step for i in range( int( stop / step ) + 1 ) ]
def setZero( target ):
attrs = [ '.translateX', '.translateY', '.translateZ', '.rotateX', '.rotateY', '.rotateZ', '.scaleX', '.scaleY', '.scaleZ' ]
for attr in attrs:
cmds.setAttr( target + attr, 0 if 'scale' not in attr else 1 )
def get_selection( limb_type ):
# Get the selection, make sure that it's three joints, raise an error if necessary and identify the type of limb we are working on
try:
[ start_jnt, mid_jnt, end_jnt ] = cmds.ls( sl = True )
except:
raise ValueError( '// Please select all the 3 FK joints of the Limb. //' )
for jnt in [ start_jnt, mid_jnt, end_jnt ]:
if not cmds.objectType( jnt, isType = 'joint' ):
raise ValueError( '// Please select joints of the limb. //' )
if limb_type == 'Arm':
half_bone_name = ['shoulder', 'elbow', 'wrist']
elif limb_type == 'Leg':
half_bone_name = ['hip', 'knee', 'ankle']
else:
raise ValueError( '// Invalid limb type. Use "Arm" or "Leg". //' )
return start_jnt, mid_jnt, end_jnt, half_bone_name
def create_system_grp( jnt, prefix ):
# Creating the system group hierarchy
system_grp = cmds.group( name = prefix + '_system', em = 1 )
cmds.matchTransform( system_grp, jnt, pos = 1, rot = 1, scl = 0 )
return system_grp
def limb_ctrl_jnt_grp( jnt, prefix, sufix, jnt_radius, system_grp ):
# Creating the root and tip CTRL joints for each system and their OFFSET
limb_ctrl_jnt = cmds.duplicate( jnt, name = prefix + sufix, po = 1 )[0]
limb_ctrl_jnt_grp = cmds.group( name = str( limb_ctrl_jnt ) + '_OFFSET', em = 1 )
cmds.setAttr( limb_ctrl_jnt + '.radius', jnt_radius )
cmds.matchTransform( limb_ctrl_jnt_grp, limb_ctrl_jnt, scl = 0 )
cmds.parent( limb_ctrl_jnt, limb_ctrl_jnt_grp )
cmds.parent( limb_ctrl_jnt_grp, system_grp )
return limb_ctrl_jnt, limb_ctrl_jnt_grp
def refine_curve( limb_crv ):
# Refine the IK Spline curve ( Changes the behaviour )
cmds.setAttr( limb_crv + '.inheritsTransform', 0 )
cmds.rebuildCurve( limb_crv, kcp = 1, d = 2, name = limb_crv + '_rebuildCrv' )
cmds.setAttr( limb_crv + '.spans', 8 )
### SETUPS
def create_bend_control_joints( start_jnt, prefix, jnt_radius, root_jnt_grp, tip_jnt_grp, system_grp, do_not_touch ):
'''
Creates the bend_jnt that drives the IK Spline
and constraint it to the existing FK joints
'''
bend_jnt = cmds.duplicate( start_jnt, name = prefix + '_Bend_CTRL_JNT', po = 1 )[0]
cmds.setAttr( bend_jnt + '.radius', jnt_radius )
bend_jnt_grp = cmds.group( name = str( bend_jnt ) + '_OFFSET', em = 1 )
cmds.pointConstraint( root_jnt_grp, tip_jnt_grp, bend_jnt_grp, name = bend_jnt + '_parCstr', mo = 0 )
aim_up_LOC = cmds.createNode( 'locator', name = bend_jnt + '_aim_up_LOCShape' )
aim_up_LOC = cmds.listRelatives( aim_up_LOC, p = 1 )[0]
cmds.rename( aim_up_LOC, bend_jnt + '_aim_up_LOC' )
cmds.parent( aim_up_LOC, do_not_touch )
cmds.pointConstraint( root_jnt_grp, tip_jnt_grp, aim_up_LOC, mo = 0, name = aim_up_LOC + '_pntCstr' )
cmds.orientConstraint( root_jnt_grp, tip_jnt_grp, aim_up_LOC, mo = 0, name = aim_up_LOC + '_oriCstr' )
cmds.setAttr( aim_up_LOC + '_oriCstr.interpType', 2 ) # ( with Shortest parameter )
cmds.aimConstraint( tip_jnt_grp, bend_jnt_grp,
aimVector = ( 0, 1, 0 ),
upVector = ( 1, 0, 0 ),
worldUpType = 'objectrotation',
worldUpVector = ( 1, 0, 0 ),
worldUpObject = aim_up_LOC,
name = bend_jnt + '_aimCstr', mo = 0 )
cmds.parent( bend_jnt, bend_jnt_grp )
setZero( bend_jnt )
cmds.parent( bend_jnt_grp, system_grp )
bend_jnt_CTRL, __ = cmds.circle( name = bend_jnt + '_CTRL', nr = ( 0, 1, 0 ), r = 7 )
cmds.delete( __ )
cmds.parent( bend_jnt_CTRL, bend_jnt_grp )
setZero( bend_jnt_CTRL )
cmds.parent( bend_jnt, bend_jnt_CTRL )
return bend_jnt
def create_SKIN_joints( num_SKIN_jnts, trans_y, root_jnt, prefix, jnt_radius, system_grp, tip_jnt ):
'''
Creates the skinning joints
'''
nb_jnts = int( num_SKIN_jnts )
trans_y_jnts = trans_y / nb_jnts
limb_01_SKIN = cmds.duplicate( root_jnt, name = prefix + '_01_SKIN', po = 1 )[0]
cmds.setAttr( limb_01_SKIN + '.radius', jnt_radius/2 )
cmds.parent( limb_01_SKIN, system_grp )
limb_SKIN_jnts = [ limb_01_SKIN ]
for i in range( nb_jnts ):
limb_SKIN = cmds.duplicate( limb_01_SKIN, name = prefix + '_0' + str( i + 2 ) + '_SKIN', po = 1 )[0]
cmds.parent( limb_SKIN, limb_SKIN_jnts[-1] )
cmds.setAttr( limb_SKIN + '.translateY', trans_y_jnts )
limb_SKIN_jnts.append( limb_SKIN )
cmds.matchTransform( limb_SKIN_jnts[-1], tip_jnt, pos = 1, rot = 0, scl = 0 )
limb_SKIN_jnts[0] = cmds.rename( limb_SKIN_jnts[0], prefix + '_01_notSKIN' )
limb_SKIN_jnts[-1] = cmds.rename( limb_SKIN_jnts[-1], prefix + '_tip_notSKIN' )
return limb_SKIN_jnts, nb_jnts
def create_ik_spline( prefix, limb_SKIN_jnts, do_not_touch, root_jnt, bend_jnt, tip_jnt, trans_y ):
'''
Creates the IK Spline on the skinning joints,
skin the curve to the Root, Bend and Tip joints
and set the advanced twist controls
'''
limb_ik_hdl, limb_eff, limb_crv = cmds.ikHandle( name = prefix + '_IK_HDL', sol = 'ikSplineSolver', sj = limb_SKIN_jnts[0], ee = limb_SKIN_jnts[-1] )
limb_crv = cmds.rename( limb_crv, prefix + '_crv' )
limb_eff = cmds.rename( limb_eff, prefix + '_eff' )
cmds.parent( limb_eff, limb_SKIN_jnts[-1] )
cmds.parent( limb_crv, limb_ik_hdl, do_not_touch )
cmds.select( root_jnt, bend_jnt, tip_jnt, limb_crv )
cmds.skinCluster( name = limb_crv + '_SKINCluster' )
if abs( trans_y ) == trans_y:
attr = [ 2, 6, 1 ]
else:
attr = [ 3, 7, -1 ]
cmds.setAttr( limb_ik_hdl + '.dTwistControlEnable', 1 )
cmds.setAttr( limb_ik_hdl + '.dWorldUpType', 4 )
cmds.setAttr( limb_ik_hdl + '.dForwardAxis', attr[0] )
cmds.setAttr( limb_ik_hdl + '.dWorldUpAxis', attr[1] )
cmds.setAttr( limb_ik_hdl + '.dWorldUpVectorX', attr[2] )
cmds.setAttr( limb_ik_hdl + '.dWorldUpVectorY', 0 )
cmds.setAttr( limb_ik_hdl + '.dWorldUpVectorZ', 0 )
cmds.connectAttr( root_jnt + '.worldMatrix[0]', limb_ik_hdl + '.dWorldUpMatrix', f = 1 )
cmds.setAttr( limb_ik_hdl + '.dWorldUpVectorEndX', attr[2] )
cmds.setAttr( limb_ik_hdl + '.dWorldUpVectorEndY', 0 )
cmds.setAttr( limb_ik_hdl + '.dWorldUpVectorEndZ', 0 )
cmds.connectAttr( tip_jnt + '.worldMatrix[0]', limb_ik_hdl + '.dWorldUpMatrixEnd', f = 1 )
return limb_crv
def create_squash_and_stretch( nb_jnts, prefix, limb_crv, trans_y, limb_SKIN_jnts, start_jnt ):
'''
Creates the stretch with pointOnCurveInfo and distanceBetween node connections
and the squash with multiplyDivide nodes ( Global Scale ) with the value of scale from the joints
'''
# pointOnCurveInfo Nodes
poci_nodes = []
range_step = 1/( nb_jnts ) # 0.25 = 1/( 5-1 ) et 5 = number of joints that you create total for in between
param = float_range( 0, 1, range_step )
for i in range( nb_jnts + 1 ):
poci = cmds.createNode( 'pointOnCurveInfo', name = prefix + '_poci_0' + str( i + 1 ) )
poci_nodes.append( poci )
cmds.connectAttr( limb_crv + 'Shape.worldSpace', poci + '.inputCurve', f = 1 )
cmds.setAttr( poci + '.turnOnPercentage', 1 )
cmds.setAttr( poci + '.parameter', param[i] )
cmds.setAttr( poci + '.parameter', k = 1 )
# distanceBetween AND MultiplyDivide ( Global Scale ) Nodes
dist_btw_nodes = []
for i in range( nb_jnts ):
dist_btw = cmds.createNode( 'distanceBetween', name = prefix + '_dist_btw_0' + str( i + 1 ) )
dist_btw_nodes.append( dist_btw )
cmds.connectAttr( poci_nodes[int( i )] + '.position', dist_btw + '.point1', f = 1 )
cmds.connectAttr( poci_nodes[int( i + 1 )] + '.position', dist_btw + '.point2', f = 1 )
mult_div = cmds.createNode( 'multiplyDivide', name = prefix + '_globalScale_0' + str( i + 1 ) )
cmds.setAttr( mult_div + '.operation', 2 )
cmds.connectAttr( dist_btw + '.distance', mult_div + '.input1X', f = 1 )
if abs( trans_y ) == trans_y:
cmds.connectAttr( 'main_CTRL.scaleY', mult_div + '.input2X', f = 1 )
else:
mult_dbl_lin = cmds.createNode( 'multDoubleLinear', name = prefix + '_inversemult_dbl_lin_0' + str( i + 1 ) )
cmds.setAttr( mult_dbl_lin + '.input2', -1 )
cmds.connectAttr( 'main_CTRL.scaleY', mult_dbl_lin + '.input1', f = 1 )
cmds.connectAttr( mult_dbl_lin + '.output', mult_div + '.input2X', f = 1 )
cmds.connectAttr( mult_div + '.outputX', limb_SKIN_jnts[int( i + 1 )] + '.translateY', f = 1 )
# squash on the SKIN jnts with the value of scale from selected joints
stretch_condition = cmds.listConnections( start_jnt, s = 1, type = 'condition' )[0]
vc_pow = cmds.createNode( 'multiplyDivide', name = prefix + '_volumicConservation_pow' )
cmds.connectAttr( stretch_condition + '.outColorR', vc_pow + '.input1X', f = 1 )
cmds.setAttr( vc_pow + '.operation', 3 )
cmds.setAttr( vc_pow + '.input2X', 0.5 )
vc_invert = cmds.createNode( 'multiplyDivide', name = prefix + '_volumicConservation_invert' )
cmds.connectAttr( vc_pow + '.outputX', vc_invert + '.input2X', f = 1 )
cmds.setAttr( vc_invert + '.operation', 2 )
cmds.setAttr( vc_invert + '.input1X', 1 )
# connect the result of each limb_SKIN_jnts jnt scale X and Z
for i in range( nb_jnts ):
cmds.connectAttr( vc_invert + '.outputX', limb_SKIN_jnts[int( i )] + '.scaleX', f = 1 )
cmds.connectAttr( vc_invert + '.outputX', limb_SKIN_jnts[int( i )] + '.scaleZ', f = 1 )
def create_half_bone( position, limb_parts, joint, previous_jnt, side_name, half_bone_name, jnt_radius, trans_y ):
half_bone = cmds.duplicate( joint, name = side_name + half_bone_name + '_HalfBone_JNT', po = 1 )[0]
half_bone_grp = cmds.group( name = str( half_bone ) + '_OFFSET', em = 1 )
cmds.setAttr( half_bone + '.radius', jnt_radius*2 )
cmds.matchTransform( half_bone_grp, half_bone, scl = 0 )
cmds.parent( half_bone, half_bone_grp )
cmds.pointConstraint( joint, half_bone, name = half_bone + '_pntCstr', mo = 0 )
ori_cstr = cmds.orientConstraint( previous_jnt, joint, half_bone, name = half_bone + '_oriCstr', mo = 1 )[0]
cmds.setAttr( ori_cstr + '.interpType', 2 ) # ( with Shortest parameter )
if position == 'mid':
half_bone_CTRL, __ = cmds.circle( name = half_bone + '_CTRL', nr = ( 0, 1, 0 ), r = 10 )
cmds.delete( __ )
cmds.parent( half_bone_CTRL, half_bone )
setZero( half_bone_CTRL )
cmds.parent( limb_parts['upper']['ctrl_jnt_grp'][1], half_bone_CTRL ) # parent upper_limb_tip_jnt_grp under the halfbone
cmds.parent( limb_parts['lower']['ctrl_jnt_grp'][0], half_bone_CTRL ) # parent lower_limb_root_jnt_grp under the halfbone
if position == 'root':
cmds.parent( limb_parts['upper']['ctrl_jnt_grp'][0], half_bone ) # parent upper_limb_root_jnt_grp under the halfbone
half_bone_SKIN = cmds.duplicate( half_bone, name = side_name + half_bone_name + '_HalfBone_SKIN', po = 1 )[0]
cmds.setAttr( half_bone_SKIN + '.radius', jnt_radius/2 )
cmds.parent( half_bone_SKIN, half_bone_CTRL if position == 'mid' else half_bone )
if position == 'tip':
aim_LOC = cmds.createNode( 'locator', name = half_bone_SKIN + '_aim_LOCShape' )
aim_LOC = cmds.listRelatives( aim_LOC, p = 1 )[0]
cmds.rename( aim_LOC, half_bone_SKIN + '_aim_LOC' )
cmds.parent( aim_LOC, half_bone )
setZero( aim_LOC )
cmds.setAttr( aim_LOC + '.translateY', trans_y )
if abs( trans_y ) == trans_y:
main_axis = 1
else:
main_axis = -1
cmds.aimConstraint( aim_LOC, half_bone_SKIN,
aimVector = ( 0, main_axis, 0 ),
upVector = ( 1, 0, 0 ),
worldUpType = 'objectrotation',
worldUpVector = ( 1, 0, 0 ),
worldUpObject = joint,
name = half_bone_SKIN + '_aimCstr', mo = 0 )
cmds.parent( limb_parts['lower']['ctrl_jnt_grp'][1], half_bone_SKIN ) # parent lower_limb_tip_jnt_grp under the halfbone
return half_bone_grp
### MAIN CODE
def create_bendy_limb( limb_type, num_SKIN_jnts_up, num_SKIN_jnts_low, jnt_radius ):
start_jnt, mid_jnt, end_jnt, half_bone_name = get_selection( limb_type )
side_name = start_jnt.split( '_' )[0] + '_'
num_SKIN_jnts = [ num_SKIN_jnts_up, num_SKIN_jnts_low ]
limb_parts = {
"upper": {
"root_jnt": start_jnt,
"tip_jnt": mid_jnt,
"num_SKIN_jnts": num_SKIN_jnts_up,
},
"lower": {
"root_jnt": mid_jnt,
"tip_jnt": end_jnt,
"num_SKIN_jnts": num_SKIN_jnts_low,
},
}
do_not_touch = cmds.group( name = side_name + limb_type + '_do_not_touch', em = 1 )
for uplo, info in limb_parts.items():
root_jnt = info["root_jnt"]
tip_jnt = info["tip_jnt"]
num_SKIN_jnts = info["num_SKIN_jnts"]
trans_y = cmds.getAttr(tip_jnt + ".translateY") # trans_y_jnt = tip_jnt
prefix = side_name + uplo + limb_type
# Creates each bendy system : ctrl joints, skin joints, ik spline with refined curve and squash and stretch
system_grp = create_system_grp(root_jnt, prefix)
limb_parts[uplo]["system_grp"] = system_grp
root_ctrl_jnt, root_ctrl_jnt_grp = limb_ctrl_jnt_grp(
root_jnt, prefix, "_Root_CTRL_JNT", jnt_radius, system_grp
)
tip_ctrl_jnt, tip_ctrl_jnt_grp = limb_ctrl_jnt_grp(
tip_jnt, prefix, "_Tip_CTRL_JNT", jnt_radius, system_grp
)
limb_parts[uplo]["ctrl_jnt_grp"] = [root_ctrl_jnt_grp, tip_ctrl_jnt_grp]
bend_jnt = create_bend_control_joints(
root_jnt,
prefix,
jnt_radius,
root_ctrl_jnt_grp,
tip_ctrl_jnt_grp,
system_grp,
do_not_touch,
)
limb_SKIN_jnts, nb_jnts = create_SKIN_joints(
num_SKIN_jnts,
trans_y,
root_ctrl_jnt,
prefix,
jnt_radius,
system_grp,
tip_ctrl_jnt,
)
limb_crv = create_ik_spline(
prefix,
limb_SKIN_jnts,
do_not_touch,
root_ctrl_jnt,
bend_jnt,
tip_ctrl_jnt,
trans_y,
)
create_squash_and_stretch(
nb_jnts, prefix, limb_crv, trans_y, limb_SKIN_jnts, root_jnt
)
refine_curve(limb_crv)
# Creates the root HalfBone joint ( shoulder or hip ) and constrains it to the existing FK joint and upper system
root_half_bone_grp = create_half_bone(
"root",
limb_parts,
start_jnt,
cmds.listRelatives(start_jnt, p=1)[0],
side_name,
half_bone_name[0],
jnt_radius,
trans_y,
)
# Creates the mid HalfBone joint ( elbow or knee ) and constrains it to the upper and lower systems
mid_half_bone_grp = create_half_bone(
"mid",
limb_parts,
mid_jnt,
start_jnt,
side_name,
half_bone_name[1],
jnt_radius,
trans_y,
)
# Creates the tip HalfBone joint ( wrist or ankle ) and constrains it to the lower system and the existing FK joint
tip_half_bone_grp = create_half_bone(
"tip",
limb_parts,
end_jnt,
mid_jnt,
side_name,
half_bone_name[2],
jnt_radius,
trans_y,
)
# Final Offset Organisation
ik_spline_limb_grp = cmds.group(
name=side_name + limb_type + "_IkSpline_limb_OFFSET", em=1
)
cmds.parent(
limb_parts["upper"]["system_grp"],
limb_parts["lower"]["system_grp"],
root_half_bone_grp,
mid_half_bone_grp,
tip_half_bone_grp,
do_not_touch,
ik_spline_limb_grp,
)
om.MGlobal.displayInfo( "// Result: " + side_name + limb_type + "_IkSpline_Limb created // " )