import maya.cmds as cmds
import maya.mel as mel
import math
import sys
from maya.cmds import*
class Curve_Rigger:
def __init__(self):
print("请选择起始和结束定位器 / Select start and end locators")
pass
def FolRivet(self):
sel=ls(sl=1)
index=len(sel)
shape=listRelatives(sel[-1],type='shape')[0]
if objExists("Rivet_Fol_Group"):
grp="Rivet_Fol_Group"
else:
grp=group(em=1,n="Rivet_Fol_Group")
for i in range(0,index-1):
loc=sel[i]
decompNode=shadingNode('decomposeMatrix',au=1)
if nodeType(shape)=='mesh':
cposNode=shadingNode('closestPointOnMesh',au=1)
connectAttr(shape+'.worldMatrix[0]',cposNode+'.inputMatrix')
connectAttr(shape+'.outMesh',cposNode+'.inMesh')
elif nodeType(shape)=='nurbsSurface':
cposNode=shadingNode('closestPointOnSurface',au=1)
connectAttr(shape+'.worldSpace',cposNode+'.inputSurface')
connectAttr(loc+'.worldMatrix[0]',decompNode+'.inputMatrix')
connectAttr(decompNode+'.outputTranslate',cposNode+'.inPosition')
UVal=getAttr(cposNode+'.parameterU')
VVal=getAttr(cposNode+'.parameterV')
follicle_name = loc + "_fol"
follicle_shape_name = follicle_name + "Shape"
# 创建毛囊 / Create follicle
follicle_shape = createNode("follicle", name=follicle_shape_name)
follicle = listRelatives(follicle_shape, p=1)[0]
rename(follicle,follicle_name)
if nodeType(shape)=='mesh':
connectAttr(shape + ".worldMesh[0]", follicle_shape + ".inputMesh")
connectAttr(shape+'.worldMatrix[0]',follicle_shape+'.inputWorldMatrix')
elif nodeType(shape)=='nurbsSurface':
connectAttr(shape + ".local", follicle_shape + ".inputSurface")
connectAttr(shape+'.worldMatrix[0]',follicle_shape+'.inputWorldMatrix')
setAttr(follicle_shape+'.parameterU',UVal)
setAttr(follicle_shape+'.parameterV',VVal)
connectAttr(follicle_shape+'.outTranslate',follicle+'.t')
connectAttr(follicle_shape+'.outRotate',follicle+'.r')
parentConstraint(follicle,loc,mo=1)
parent(follicle,grp)
delete(cposNode,decompNode)
def create_cylinder_with_locators(self,name, start_locator, end_locator, radius=0.5, divisions=20):
# 获取定位器的位置 / Get the positions of the locators
start_position = cmds.xform(start_locator, query=True, translation=True, worldSpace=True)
end_position = cmds.xform(end_locator, query=True, translation=True, worldSpace=True)
# 计算圆柱体的高度和方向向量 / Calculate the height and direction vector of the cylinder
height = math.sqrt(sum((end - start) ** 2 for start, end in zip(start_position, end_position)))
direction = [(end - start) / height for start, end in zip(start_position, end_position)]
# 使用指定参数创建圆柱体 / Create a cylinder with the specified parameters
cylinder = cmds.polyCylinder(n=name, radius=radius, height=height, subdivisionsY=divisions, axis=direction)[0]
# 将圆柱体定位在两个定位器的中点 / Position the cylinder at the midpoint between the locators
midpoint = [(start + end) / 2 for start, end in zip(start_position, end_position)]
cmds.move(midpoint[0], midpoint[1], midpoint[2], cylinder)
return cylinder
def create_curve_from_objects(self,objects):
store_positions = [cmds.xform(obj, query=True, worldSpace=True, translation=True) for obj in objects]
degree = 3 # 如需更改度数请在此修改 / Change the degree here if needed
build_curve = "curve -d {} ".format(degree)
for pos in store_positions:
build_curve += "-p {} {} {} ".format(pos[0], pos[1], pos[2])
return mel.eval(build_curve)
def create_locator_in_direction(self,locator1, locator2):
pos1 = cmds.pointPosition(locator1)
pos2 = cmds.pointPosition(locator2)
# 计算方向和距离 / Calculate direction and distance
direction_vector = [(pos2[0] - pos1[0]), (pos2[1] - pos1[1]), (pos2[2] - pos1[2])]
direction_length = math.sqrt(sum(v ** 2 for v in direction_vector))
normalized_direction = [(v / direction_length) * 10 * direction_length for v in direction_vector]
new_locator_pos = [pos1[i] + normalized_direction[i] for i in range(3)]
# 创建新定位器 / Create new locator
new_locator = cmds.spaceLocator(name="new_locator")[0]
cmds.move(new_locator_pos[0], new_locator_pos[1], new_locator_pos[2], new_locator)
return new_locator
def create_control_curve(self,pref, shape_type,obj,col):
if shape_type == 'circle':
ctrl = cmds.circle(ch=0, n=pref +obj.replace('Jnt','Ctrl'), r=0.5, nr=(1, 0, 0))[0]
elif shape_type == 'cube':
mel.eval('$ctrl =`curve -d 1 -p 0.5 0.5 0.5 -p 0.5 0.5 -0.5 -p -0.5 0.5 -0.5 -p -0.5 -0.5 -0.5 -p 0.5 -0.5 -0.5 -p 0.5 0.5 -0.5 -p -0.5 0.5 -0.5 -p -0.5 0.5 0.5 -p 0.5 0.5 0.5 -p 0.5 -0.5 0.5 -p 0.5 -0.5 -0.5 -p -0.5 -0.5 -0.5 -p -0.5 -0.5 0.5 -p 0.5 -0.5 0.5 -p -0.5 -0.5 0.5 -p -0.5 0.5 0.5 -k 0 -k 1 -k 2 -k 3 -k 4 -k 5 -k 6 -k 7 -k 8 -k 9 -k 10 -k 11 -k 12 -k 13 -k 14 -k 15 -n "'+pref +obj.replace('Jnt','Ctrl')+'" `;') # 立方体曲线定义 / Replace with cube curve definition
ctrl = cmds.rename(pref +obj.replace('Jnt','Ctrl'))
shape = cmds.pickWalk(d="down")[0]
cmds.rename(shape, ctrl + "Shape")
ctrl_sdk_grp = cmds.group(n=pref +obj.replace('Jnt','Ctrl')+'_SdkGrp')
ctrl_off_grp = cmds.group(n=pref +obj.replace('Jnt','Ctrl')+'_OffGrp')
# 自定义控制器外观 / Customize control appearance
cmds.setAttr(ctrl + '.overrideEnabled', 1)
cmds.setAttr(ctrl + '.overrideColor', col)
sub_ctrl = cmds.circle(ch=0, n=pref +obj.replace('Jnt','Sub_Ctrl'))[0]
sub_ctrl_sdk_grp = cmds.group(n=pref +obj.replace('Jnt','Sub_Ctrl')+'_SdkGrp')
sub_ctrl_off_grp = cmds.group(n=pref +obj.replace('Jnt','Sub_Ctrl')+'_OffGrp')
# 自定义子控制器外观 / Customize sub control appearance
cmds.setAttr(sub_ctrl + '.overrideEnabled', 1)
cmds.setAttr(sub_ctrl + '.overrideColor', 13)
cmds.parent(sub_ctrl_off_grp, ctrl)
cmds.addAttr(ctrl, ln='Sub_Ctrl_Vis', at='bool', h=0, k=1, r=1)
cmds.connectAttr(ctrl + '.Sub_Ctrl_Vis', sub_ctrl_off_grp + '.v')
delete(parentConstraint(obj,ctrl_off_grp,mo=0))
return ctrl
def create_joints_between_objects(self,num_joints, prefix=''):
joints = []
selObj=ls(sl=1)
start_obj = selObj[0]
end_obj = selObj[1]
for i in range(num_joints + 2):
select(cl=1)
t = float(i) / float(num_joints + 1)
joint_name = '{}_{:02d}_Jnt'.format(prefix, i)
new_joint = cmds.joint(name=joint_name)
start_pos = cmds.xform(start_obj, q=1, t=1, worldSpace=True)
end_pos = cmds.xform(end_obj, q=1, t=1, worldSpace=True)
joint_pos = [
start_pos[0] + (end_pos[0] - start_pos[0]) * t,
start_pos[1] + (end_pos[1] - start_pos[1]) * t,
start_pos[2] + (end_pos[2] - start_pos[2]) * t
]
cmds.xform(new_joint, translation=joint_pos, worldSpace=True)
if not i == num_joints + 1:
delete(aimConstraint(end_obj,new_joint,aim=(1,0,0),u=(0,1,0),wut='vector',wu=(0,1,0)))
else:
delete(aimConstraint(start_obj,end_obj,new_joint,aim=(-1,0,0),u=(0,1,0),wut='vector',wu=(0,1,0)))
select(new_joint)
if i>>0:
makeIdentity( a = True, t =0, r= 1, s =0, n= 0, pn= 1);
joints.append(new_joint)
return joints
def create_ui(self,path=''):
window_name = "RigNet_Tools"
if cmds.window(window_name, exists=True):
cmds.deleteUI(window_name)
cmds.window(window_name, title="曲线绑定工具 / Curve Rigger Tools",w=350,h=430)
form=cmds.formLayout(numberOfDivisions=100,w = 350)
txt=cmds.text(label="蒙皮关节数量 / Number of Skn Joints:")
skn_joints_field = cmds.intField(minValue=1, value=10,w=80)
FkTxt=cmds.text(label="控制器数量 / Number of Controls:")
flst_field = cmds.intField(minValue=1, value=5,w=80)
IKChk=cmds.checkBox(label="IK控制器 / IK Controls")
TwistChk=cmds.checkBox(label="启用扭曲 / Enable Twist")
FKChk=cmds.checkBox(label="FK控制器 / FK Controls")
RevChk=cmds.checkBox(label="反向FK控制器 / RevFK Controls")
PathChk=cmds.checkBox(label="路径控制器 / Path Controls",cc=lambda state: self.enable_flst_field(state,path_joints_field))
PText = cmds.text(label="路径控制器数量 / Number of Path Controls:",)
path_joints_field = cmds.intField(minValue=1, value=50,en=0,w=80)
but=cmds.button(label="创建绑定 / Create Rig", command=lambda args: self.create_rig(skn_joints_field, flst_field,path_joints_field,IKChk,FKChk,RevChk,PathChk,TwistChk),bgc=(0.15,0.15,0.15),w=310,h=40)
transfer_but=cmds.button(label="转移蒙皮权重 / Transfer Skin Weights", command=lambda args: self.transfer_skin_weights(),bgc=(0.2,0.3,0.2),w=310,h=40)
cmds.formLayout( form, edit=True,
attachForm=[
(txt, 'top',20),
(txt, 'left',20),
(skn_joints_field, 'top',17),
(skn_joints_field, 'left',260),
(flst_field, 'top',37),
(flst_field, 'left',260),
(IKChk, 'top', 65),
(IKChk, 'left', 20),
(TwistChk, 'top', 65),
(TwistChk, 'left', 200),
(FkTxt, 'top', 40),
(FkTxt, 'left', 20),
(FKChk, 'top', 90),
(FKChk, 'left', 20),
(RevChk, 'top', 90),
(RevChk, 'left', 200),
(PathChk, 'top', 140),
(PathChk, 'left', 20),
(PText, 'top', 115),
(PText, 'left', 20),
(path_joints_field, 'top', 112),
(path_joints_field, 'left', 260),
(but, 'top', 170),
(but, 'left', 20),
(transfer_but, 'top', 220),
(transfer_but, 'left', 20)
])
cmds.showWindow(window_name)
def enable_flst_field(self,state, path_joints_field):
cmds.intField(path_joints_field, edit=True, enable=state,bgc=(0.0,0.0,0.0))
def transfer_skin_weights(self):
# 获取选择的模型 / Get selected model
selection = cmds.ls(sl=True)
if not selection:
cmds.warning("请先选择目标模型 / Please select target model first")
return
# 检查源模型是否存在 / Check if source model exists
source_mesh = "Dummy_Mesh"
if not cmds.objExists(source_mesh):
cmds.warning("源模型 'Dummy_Mesh' 不存在 / Source model 'Dummy_Mesh' does not exist")
return
# 获取源模型的蒙皮簇 / Get skin cluster from source mesh
source_skin_cluster = None
# 获取网格的shape节点 / Get mesh shape node
source_shapes = cmds.listRelatives(source_mesh, shapes=True, type='mesh')
if not source_shapes:
cmds.warning("源模型没有网格形状 / Source model has no mesh shape")
return
source_shape = source_shapes[0]
# 从shape节点查找蒙皮簇 / Find skin cluster from shape node
skin_clusters = cmds.listConnections(source_shape, type='skinCluster', source=True, destination=False)
if not skin_clusters:
# 尝试从历史记录中查找 / Try to find from history
history = cmds.listHistory(source_shape, pruneDagObjects=True)
if history:
for node in history:
if cmds.nodeType(node) == 'skinCluster':
skin_clusters = [node]
break
if skin_clusters:
source_skin_cluster = skin_clusters[0]
else:
cmds.warning("源模型没有蒙皮簇 / Source model has no skin cluster")
return
# 获取蒙皮关节 / Get skin joints
skin_joints = cmds.skinCluster(source_skin_cluster, query=True, influence=True)
if not skin_joints:
cmds.warning("无法获取蒙皮关节 / Cannot get skin joints")
return
# 处理每个选择的对象 / Process each selected object
for target_mesh in selection:
# 检查是否为网格 / Check if it's a mesh
target_shapes = cmds.listRelatives(target_mesh, shapes=True, type='mesh')
if not target_shapes:
cmds.warning("'{}' 不是网格对象 / '{}' is not a mesh object".format(target_mesh, target_mesh))
continue
target_shape = target_shapes[0]
# 检查目标模型是否已有蒙皮簇 / Check if target already has skin cluster
target_skin_cluster = None
target_skin_clusters = cmds.listConnections(target_shape, type='skinCluster', source=True, destination=False)
if not target_skin_clusters:
# 尝试从历史记录中查找 / Try to find from history
history = cmds.listHistory(target_shape, pruneDagObjects=True)
if history:
for node in history:
if cmds.nodeType(node) == 'skinCluster':
target_skin_clusters = [node]
break
if target_skin_clusters:
target_skin_cluster = target_skin_clusters[0]
# 如果目标模型没有蒙皮簇,创建一个 / Create skin cluster if target doesn't have one
if not target_skin_cluster:
# 创建新的蒙皮簇 / Create new skin cluster
target_skin_cluster = cmds.skinCluster(skin_joints, target_mesh, tsb=True, mi=1)[0]
# 转移蒙皮权重 / Transfer skin weights
try:
cmds.copySkinWeights(
sourceSkin=source_skin_cluster,
destinationSkin=target_skin_cluster,
noMirror=True,
surfaceAssociation='closestPoint',
influenceAssociation=['oneToOne', 'closestJoint']
)
print("成功转移蒙皮权重: {} / Successfully transferred skin weights: {}".format(target_mesh, target_mesh))
except Exception as e:
cmds.warning("转移蒙皮权重失败: {} / Failed to transfer skin weights: {}".format(str(e), str(e)))
cmds.select(selection, r=True)
def create_rig(self,skn_joints_field, flst_field,path_joints_field,IKChk,FKChk,RevChk,PathChk,TwistChk):
IK=cmds.checkBox(IKChk,q=1,v=1)
FK=cmds.checkBox(FKChk,q=1,v=1)
Rev=cmds.checkBox(RevChk,q=1,v=1)
Path=cmds.checkBox(PathChk,q=1,v=1)
Twst=cmds.checkBox(TwistChk,q=1,v=1)
print (IK,FK,Rev,Path)
num_skn_joints = cmds.intField(skn_joints_field, query=True, value=True)
num_flst = cmds.intField(flst_field, query=True, value=True)
pth_jnt=cmds.intField(path_joints_field,q=1,value=1)
initLoc=ls(sl=1)
# 创建蒙皮关节 / Create SKn Jnt #
Jlst=self.create_joints_between_objects(num_skn_joints-2,'Skn')
select(Jlst[0])
FreezeTransformations()
# 将'locator1'和'locator2'替换为你的定位器名称 / Replace 'locator1' and 'locator2' with the names of your locators
start_locator = initLoc[0]
end_locator = initLoc[1]
# 调用函数创建圆柱体 / Call the function to create the cylinder
cylinder = self.create_cylinder_with_locators("Dummy_Mesh", start_locator, end_locator, radius=0.3, divisions=num_skn_joints-1)
select(Jlst,cylinder)
SmoothBindSkin()
a=-1
select(cl=1)
for i in range(len(Jlst)):
jj=cmds.duplicate(Jlst[a],n='IK_'+Jlst[a])
grp = group(em=1,n= Jlst[a]+'_Conn_Grp')
delete(parentConstraint(Jlst[a],grp,mo=0))
parent(Jlst[a],grp)
if i>0:
parent('IK_'+Jlst[a+1],'IK_'+Jlst[a])
#parent(Jlst[a+1]+'_Conn_Grp',Jlst[a])
parentConstraint('IK_'+Jlst[a],grp,mo=1)
#connectAttr('IK_'+Jlst[a]+'.r',grp+'.r')
a=a-1
Ik_OffGrp=group(em=1,n='IK_Offset_Val_Grp')
delete(parentConstraint(Jlst[0],Ik_OffGrp,mo=0))
select(initLoc)
# 创建驱动IK关节 / Create Drv IK Jnt #
Dlst=self.create_joints_between_objects(num_flst-2,'Drv')
self.create_curve_from_objects(Dlst)
crv=ls(sl=1)[0]
refresh()
select('IK_'+Jlst[0])
FreezeTransformations()
select('IK_'+Jlst[0],'IK_'+Jlst[-1])
refresh()
# 创建IK样条 / Create IK Spline #
ik=ikHandle(sol = 'ikSplineSolver', pcv=0, c=crv, ccv= 0)
select(Dlst,crv)
SmoothBindSkin()
main_Ctrl = circle(ch=0,n='Main_Ctrl',r=5,nr=(0,1,0))[0]
main_grp = group(n='Main_Ctrl_Grp')
#delete(parentConstraint(initLoc[0],initLoc[1],main_grp,mo=0))
parent('IK_'+Jlst[0],Ik_OffGrp)
for jJnt in Jlst:
parent(jJnt+'_Conn_Grp',Ik_OffGrp)
# 添加拉伸功能 / Adding Stretch #
addAttr(main_Ctrl,ln='Maintain_Length',min=0,max=1,k=1,at='float')
crvInfo=arclen(crv,ch=1)
dis=getAttr(crvInfo+'.arcLength')
StrGlMD=createNode('multiplyDivide',n='Wire_StrVGlobal_MD')
connectAttr(crvInfo+'.arcLength',StrGlMD+'.input1X')
connectAttr(main_Ctrl+'.sx',StrGlMD+'.input2X')
setAttr(StrGlMD+'.operation',2)
StrMD=createNode('multiplyDivide',n='Wire_StrVal_MD')
setAttr(StrMD+'.input2X',dis)
connectAttr(StrGlMD+'.outputX',StrMD+'.input1X')
setAttr(StrMD+'.operation',2)
Tval=getAttr('IK_'+Jlst[-1]+'.tx')
StrValMD=createNode('multiplyDivide',n='Wire_Stretchy_MD')
setAttr(StrValMD+'.input1X',Tval)
connectAttr(StrMD+'.outputX',StrValMD+'.input2X')
setAttr(StrValMD+'.operation',1)
StrcBlnd=createNode('blendColors',n='Wire_StrSwitch_MD')
setAttr('Wire_StrSwitch_MD.color2R',Tval)
connectAttr(StrValMD+'.outputX','Wire_StrSwitch_MD.color1R')
connectAttr(main_Ctrl+'.Maintain_Length',StrcBlnd+'.blender')
for i in range(1,len(Jlst)):
connectAttr(StrcBlnd+'.outputR','IK_'+Jlst[i]+'.tx')
FkLst=[]
IKCtrl=[]
RevLst=[]
ConsLst=[]
IKGrp=[]
pthLst=[]
i=0
if FK == True:
if Rev == True:
IK = True
if IK == True:
for DJnt in Dlst:
Ctrl = self.create_control_curve('IK_', 'circle',DJnt,17)
IKCtrl.append(Ctrl)
if FK == True:
Flst=list(Dlst)
print(Dlst)
Flst.reverse()
print(Flst)
for DJnt in Flst:
Ctrl = self.create_control_curve('FK_', 'cube',DJnt,20)
FkLst.append(Ctrl)
Grp=group(em=1,n=Ctrl.replace('Ctrl','Drv_OffGrp'))
delete(parentConstraint(Ctrl,Grp,mo=0))
if i>>0:
parent(FkLst[i-1].replace('Ctrl','Ctrl_OffGrp'),Ctrl)
parent(FkLst[i-1].replace('Ctrl','Drv_OffGrp'),Grp)
i=i+1
connectAttr(Grp+'.t',Ctrl.replace('Ctrl','Ctrl_OffGrp')+'.t')
connectAttr(Grp+'.r',Ctrl.replace('Ctrl','Ctrl_OffGrp')+'.r')
connectAttr(Grp+'.s',Ctrl.replace('Ctrl','Ctrl_OffGrp')+'.s')
i=0
if Rev == True:
Revlst=list(Dlst)
print(Revlst)
for DJnt in Revlst:
Ctrl = self.create_control_curve('RevFK_', 'cube',DJnt,21)
RevLst.append(Ctrl)
Grp=group(em=1,n=Ctrl.replace('Ctrl','Drv_OffGrp'))
delete(parentConstraint(Ctrl,Grp,mo=0))
if i>>0:
parent(RevLst[i-1].replace('Ctrl','Ctrl_OffGrp'),Ctrl)
parent(RevLst[i-1].replace('Ctrl','Drv_OffGrp'),Grp)
i=i+1
connectAttr(Grp+'.t',Ctrl.replace('Ctrl','Ctrl_OffGrp')+'.t')
connectAttr(Grp+'.r',Ctrl.replace('Ctrl','Ctrl_OffGrp')+'.r')
connectAttr(Grp+'.s',Ctrl.replace('Ctrl','Ctrl_OffGrp')+'.s')
if IK == True:
i=0
for DJnt in Dlst:
parentConstraint(IKCtrl[i].replace('Ctrl','Sub_Ctrl'),DJnt,mo=1)
i=i+1
elif FK == True:
i=0
for DJnt in Flst:
parentConstraint(FkLst[i].replace('Ctrl','Sub_Ctrl'),DJnt,mo=1)
i=i+1
elif Rev == True:
i=0
for DJnt in Dlst:
parentConstraint(RevLst[i].replace('Ctrl','Sub_Ctrl'),DJnt,mo=1)
i=i+1
# 其余绑定创建代码 / Rest of the rig creation code #
a=0
b=-1
if IK:
for ikctrl in IKCtrl:
IKGrp.append(ikctrl.replace('Ctrl','Ctrl_OffGrp'))
par=[]
if FkLst:
par.append(FkLst[b])
if RevLst:
par.append(RevLst[a])
if par:
constraint = cmds.parentConstraint(par, ikctrl.replace('Ctrl','Ctrl_OffGrp'),mo=1)[0]
ConsLst.append(constraint)
a=a+1
b=b-1
if FK and Rev == True:
addAttr(main_Ctrl,ln='RevFK',min=0,max=10,k=1,at='float')
inVal=0
val=10.0/len(Dlst)
sel =list(IKCtrl)
for obj in sel:
Nobj=obj
obj = obj.replace('Ctrl','Ctrl_OffGrp')
par=parentConstraint(Nobj.replace('IK_','FK_'),Nobj.replace('IK_','RevFK_'),obj,mo=1)[0]
select(par)
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at=Nobj.replace('IK_','FK_')+'W0',dv=inVal,v=1)
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at=Nobj.replace('IK_','FK_')+'W0',dv=inVal+val,v=0)
select(Nobj.replace('IK_','FK_')+'Shape')
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at='.v',dv=inVal,v=1)
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at='.v',dv=inVal+val,v=0)
select(par)
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at=Nobj.replace('IK_','RevFK_')+'W1',dv=inVal,v=0)
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at=Nobj.replace('IK_','RevFK_')+'W1',dv=inVal+val,v=1)
select(Nobj.replace('IK_','RevFK_')+'Shape')
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at='.v',dv=inVal,v=0)
setDrivenKeyframe(cd="Main_Ctrl.RevFK",at='.v',dv=inVal+val,v=1)
inVal=inVal+val
select (Jlst)
sets(n='Bind_Skin')
pJnt=listRelatives(Jlst[0],p=1)[0]
select(Dlst)
group(n='Jnt_Grp')
scaleConstraint('Main_Ctrl','Jnt_Grp',mo=1)
select(crv,ik[0])
group(n='Extra_Grp')
dGrp=group(em=1,n='Drv_Null_Group')
parent(dGrp,'Extra_Grp')
parentConstraint('Main_Ctrl',dGrp,mo=1)
scaleConstraint('Main_Ctrl',dGrp,mo=1)
if IK == True:
addAttr(main_Ctrl,ln='IK_Ctrl_Vis',at='bool',k=1)
group(IKGrp,n='IK_Ctrl_Grp')
connectAttr('Main_Ctrl.IK_Ctrl_Vis','IK_Ctrl_Grp.v')
parent('IK_Ctrl_Grp','Main_Ctrl')
if FK == True:
select(FkLst[-1].replace('Ctrl','Ctrl_OffGrp'))
FkGrp=group(n='Fk_Ctrl_Group')
addAttr(main_Ctrl,ln='FK_Ctrl_Vis',at='bool',k=1)
connectAttr('Main_Ctrl.FK_Ctrl_Vis',FkGrp+'.v')
parent(FkGrp,'Main_Ctrl')
parent("FK_Drv_00_Drv_OffGrp",dGrp)
if Rev == True:
select(RevLst[-1].replace('Ctrl','Ctrl_OffGrp'))
RevFkGrp=group(n='RevFk_Ctrl_Group')
try:
addAttr(main_Ctrl,ln='FK_Ctrl_Vis',at='bool',k=1)
except: pass
connectAttr('Main_Ctrl.FK_Ctrl_Vis',RevFkGrp+'.v')
parent(RevFkGrp,'Main_Ctrl')
parent(RevLst[-1].replace('Ctrl','Drv_OffGrp'),dGrp)
if Path == True:
addAttr(main_Ctrl,ln='Path_Ctrl_Vis',at='bool',k=1)
self.create_locator_in_direction(initLoc[0],initLoc[1])
select(initLoc[0],"new_locator")
PathLst=self.create_joints_between_objects(pth_jnt-2,'Path')
select(PathLst)
Pcrv=self.create_curve_from_objects(PathLst)
noCv=len(PathLst)-1
rebuildCurve(Pcrv,ch=1,rpo=1,rt=0,end=1,kr=0,kcp=0,kep=1,kt=0,s=noCv,d=3,tol=0.01)
i=0
PathLst.reverse()
for PJnt in PathLst:
Ctrl = self.create_control_curve('', 'circle',PJnt,13)
pthLst.append(Ctrl)
parent(PJnt,Ctrl.replace('Ctrl','Sub_Ctrl'))
if i>>0:
try:
parent(pthLst[i-1].replace('Ctrl','Ctrl_OffGrp'),Ctrl)
except:
pass
i=i+1
parent(pthLst[-1].replace('Ctrl','Ctrl_OffGrp'),'Main_Ctrl')
connectAttr('Main_Ctrl.Path_Ctrl_Vis',pthLst[-1].replace('Ctrl','Ctrl_OffGrp')+'.v')
DummyJnt=duplicate(PathLst[0],n='Dummy_Jnt')[0]
delete(pointConstraint(Pcrv,DummyJnt,mo=0))
DCrv=duplicate(Pcrv)[0]
setAttr(DummyJnt+'.tz',0.5)
delete(pointConstraint(DummyJnt,Pcrv,mo=0))
setAttr(DummyJnt+'.tz',-0.5)
delete(pointConstraint(DummyJnt,DCrv,mo=0))
delete(DummyJnt)
select(Pcrv,DCrv,PathLst)
SmoothBindSkin()
select(Pcrv,DCrv)
surf=loft(n='Path_Surface')
DeleteHistory(surf[0])
select(surf[0],PathLst)
skinCluster(PathLst,surf[0],tsb=1,mi=1)
print(surf)
if not FK and not Rev == True:
if not IK:
select(Dlst,surf[0])
self.FolRivet()
else:
print('不存在 / Not Present')
select(IKGrp,surf[0])
self.FolRivet()
if FK:
select("FK_Drv_*_Drv_OffGrp",surf[0])
self.FolRivet()
if Rev:
select("RevFK_Drv_*_Drv_OffGrp",surf[0])
self.FolRivet()
# 创建移动绑定设置 / Create traveling Rig setup
addAttr(main_Ctrl,ln='Travel',min=0,max=10,k=1,at='float')
if FK:
fol=ls("FK_Drv_*_Drv_OffGrp_fol")
initval=getAttr(fol[-1]+"Shape.parameterV")
Diff=1.0-initval
for follicle in fol:
shp=listRelatives(follicle,c=1,f=1)[0]
inValue=getAttr(shp+'.parameterV')
select(shp)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=0,v=inValue)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=10,v=inValue+Diff)
if Rev:
fol=ls("RevFK_Drv_*_Drv_OffGrp_fol")
initval=getAttr(fol[-1]+"Shape.parameterV")
Diff=1.0-initval
for follicle in fol:
shp=listRelatives(follicle,c=1,f=1)[0]
inValue=getAttr(shp+'.parameterV')
select(shp)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=0,v=inValue)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=10,v=inValue+Diff)
if not FK and not Rev == True:
if not IK:
fol=ls("Drv_*_Jnt_fol")
initval=getAttr(fol[-1]+"|follicleShape.parameterV")
Diff=1.0-initval
for follicle in fol:
shp=listRelatives(follicle,c=1,f=1)[0]
inValue=getAttr(shp+'.parameterV')
select(shp)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=0,v=inValue)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=10,v=inValue+Diff)
if IK:
fol=ls("IK_Drv_*_Ctrl_OffGrp_fol")
initval=getAttr(fol[-1]+"Shape.parameterV")
Diff=1.0-initval
for follicle in fol:
shp=listRelatives(follicle,c=1,f=1)[0]
inValue=getAttr(shp+'.parameterV')
select(shp)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=0,v=inValue)
setDrivenKeyframe(cd="Main_Ctrl.Travel",at='parameterV',dv=10,v=inValue+Diff)
# 在蒙皮关节上添加扭曲 / Adding Twist on Skin Jnts
if Twst:
select(cl=1)
DummyJnt=duplicate(Jlst[0],n='Dummy_Jnt')[0]
delete(pointConstraint(crv,DummyJnt,mo=0))
TlocLst=[]
DnumJnt=len(Dlst)-1
LCrv=duplicate(crv)[0]
rebuildCurve(LCrv,ch=1,rpo=1,rt=0,end=1,kr=0,kcp=0,kep=1,kt=0,s=DnumJnt,d=3,tol=0.01)
RCrv=duplicate(LCrv)[0]
axis=['tx','ty','tz']
for ax in axis:
setAttr(LCrv+'.'+ax,l=0)
setAttr(RCrv+'.'+ax,l=0)
setAttr(DummyJnt+'.tz',0.5)
delete(pointConstraint(DummyJnt,LCrv,mo=0))
setAttr(DummyJnt+'.tz',-0.5)
delete(pointConstraint(DummyJnt,RCrv,mo=0))
delete(DummyJnt)
select(LCrv,RCrv)
Tsurf=loft(n='Twist_Surface')
DeleteHistory(Tsurf[0])
select(Tsurf[0],Dlst)
skinCluster(Dlst,Tsurf[0],tsb=1,mi=1)
for SknJnt in Jlst:
Tloc=spaceLocator(n=SknJnt.replace('Jnt','_Twst_Loc'))[0]
Tgrp=group(n=Tloc+'_Grp')
TlocLst.append(Tgrp)
delete(parentConstraint(SknJnt,Tgrp,mo=0))
parentConstraint(Tloc,SknJnt,st=('x','y','z'),sr=('y','z'),mo=1)
select(TlocLst,Tsurf[0])
self.FolRivet()
for grp in TlocLst:
delete(listRelatives(grp,ad=1,type='constraint'))
parent(grp,grp+'_fol')
parent('Twist_Surface','Extra_Grp')
delete(LCrv,RCrv)
# 创建外层控制器并整理层级 / Create outer controllers and organize hierarchy
select(cl=True) # 清除选择,确保组是空的 / Clear selection to ensure groups are empty
# 创建中间层控制器 / Create middle layer controller
middle_Ctrl = circle(ch=0,n='Middle_Ctrl',r=6,nr=(0,1,0))[0]
select(cl=True) # 清除选择 / Clear selection
middle_Ctrl_off_grp = group(em=True, n='Middle_Ctrl_OffGrp')
delete(parentConstraint(main_Ctrl,middle_Ctrl_off_grp,mo=0))
setAttr(middle_Ctrl + '.overrideEnabled', 1)
setAttr(middle_Ctrl + '.overrideColor', 14) # 黄色 / Yellow
# 创建最外层控制器 / Create outer layer controller
select(cl=True) # 清除选择 / Clear selection
outer_Ctrl = circle(ch=0,n='Outer_Ctrl',r=7,nr=(0,1,0))[0]
select(cl=True) # 清除选择 / Clear selection
outer_Ctrl_off_grp = group(em=True, n='Outer_Ctrl_OffGrp')
delete(parentConstraint(middle_Ctrl,outer_Ctrl_off_grp,mo=0))
setAttr(outer_Ctrl + '.overrideEnabled', 1)
setAttr(outer_Ctrl + '.overrideColor', 18) # 红色 / Red
# 建立层级关系 / Establish hierarchy: Outer_Ctrl -> Middle_Ctrl -> Main_Ctrl_Grp
# 从内到外建立层级:先将 Main_Ctrl_Grp 放入 Middle_Ctrl
main_grp_parent = listRelatives(main_grp, p=True)
if not main_grp_parent or main_grp_parent[0] != middle_Ctrl:
parent(main_grp, middle_Ctrl)
# 然后将 Middle_Ctrl 放入其 OffGrp
middle_Ctrl_parent = listRelatives(middle_Ctrl, p=True)
if not middle_Ctrl_parent or middle_Ctrl_parent[0] != middle_Ctrl_off_grp:
parent(middle_Ctrl, middle_Ctrl_off_grp)
# 将 Middle_Ctrl_OffGrp 放入 Outer_Ctrl
middle_off_grp_parent = listRelatives(middle_Ctrl_off_grp, p=True)
if not middle_off_grp_parent or middle_off_grp_parent[0] != outer_Ctrl:
parent(middle_Ctrl_off_grp, outer_Ctrl)
# 将 Outer_Ctrl 放入其 OffGrp
outer_Ctrl_parent = listRelatives(outer_Ctrl, p=True)
if not outer_Ctrl_parent or outer_Ctrl_parent[0] != outer_Ctrl_off_grp:
parent(outer_Ctrl, outer_Ctrl_off_grp)
# 清理 / Cleanup
parent(Ik_OffGrp,'Jnt_Grp')
delete(initLoc)
if Path:
delete(Pcrv,DCrv,'new_locator')
parent('Path_Surface','Extra_Grp')
try:
parent('Rivet_Fol_Group','Extra_Grp')
except:pass
select('Dummy_Mesh')
RefGrp=group(n='Ref_Geo_Grp')
select(RefGrp,'Outer_Ctrl_OffGrp','Jnt_Grp','Extra_Grp')
group(n='Rig_Grp')
setAttr('Extra_Grp.v',0)
setAttr('Jnt_Grp.v',0)
rope_rig_instance = Curve_Rigger()
rope_rig_instance.create_ui()
This video demonstrates how to rig flexible props such as rope, tail, tentacle, chain and vine in Maya using Curve Rigger.
本期视频演示如何在 Maya 中,使用 Curve Rigger 快速绑定线条类道具(绳子、尾巴、触手、藤蔓等)。
With only 2 locators, you can automatically create joints, FK / IK, Reverse FK, stretch, twist and path animation controls.
只需要 2 个定位器,即可一键生成骨骼、FK / IK、反向 FK、拉伸、扭曲以及路径动画控制。
⚠️ This tool rigs flexible props USING curves, not rigging the curve itself.
⚠️ 本工具是“通过曲线来绑定道具”,而不是去绑定曲线本身。
🔹 Tool Features / 功能亮点
- One-click rig for flexible props (Rope / Tail / Tentacle / Vine / Chain)
- FK / IK / Reverse FK switching
- Stretch & twist control
- Path-based animation control
- Auto skin and one-click skin weight transfer
- Bilingual UI (Chinese / English)
- 线条类道具一键自动绑定
- 支持 FK / IK / 反向 FK 切换
- 支持拉伸、扭曲控制
- 路径动画控制系统
- 蒙皮权重一键转移
- 中英文双语界面优化
🔹 Suitable For / 适用类型
- Rope / Cable / Hose rig
- Tail & Tentacle rig
- Vine, whip and flexible props
- Animation, game and cartoon production
- 绳子、管线、软管类道具
- 尾巴、触手类结构
- 藤蔓、鞭子等线条道具
- 动画、游戏、卡通项目
🔹 Workflow Overview / 绑定流程
- Place 2 locators to define the start and end of the prop
- Run Curve Rigger to generate the rig automatically
- Animate using FK, IK or path controls
- 放置 2 个定位器作为起点和终点
- 运行 Curve Rigger 自动生成绑定
- 使用 FK / IK 或路径控制进行动画
🔹 Software
Autodesk Maya 2018+