tsStickyLips tool

This tool will let you easily create a sticky lips setup for your character. The setup created is based on a wire deformer.


1. Download the script from this link: tsStickyLips.rar;

2. Copy file tsStickyLips.pyc into your maya scripts/ folder;

3. From the script editor, in a python tab type:

import tsStickyLips



As the sticky lips deformation lies on top of other deformations, this should be your very last step in facial rigging.

1. Select top edges for the uplip and press ‘<<<’;

2.  Select top edges for the bottomlip and press ‘<<<’;

3. Press ‘Create stickyLips’ button.

The script will create a copy of the geometry with sticky lips setup. You can hide the previous geo and go rendering with this copy.


Optional parameters:

The tool provides automatically on weighting the wire deformer influence areas. Two optional parameters can be tweaked to change how the weighting is done:

- Selection growth: Grows vertex selection before setting weight influence to 1;

- Smoothness: How many times the weighting should be smoothed.


Here’s the source code of the main procedure for the tech guys:

def createStyckyLips(top_edges, bottom_edges, selection_growth = 2, smoothing=8):
 base_mesh = top_edges[0].split(".")[0]

 # Create curve on top edge sel on duplicated mesh
 top_curve = cmds.polyToCurve(form=2, degree=1)[0]
 top_curve_shape = cmds.listRelatives(top_curve, c=1)[0]

 # Create curve on bottom edge sel on duplicated mesh
 bottom_curve = cmds.polyToCurve(form=2, degree=1)[0]
 bottom_curve_shape = cmds.listRelatives(bottom_curve, c=1)[0]

 # Create wire average curve
 avg_node = cmds.createNode("avgCurves")
 cmds.setAttr(avg_node + ".automaticWeight", 0)
 cmds.setAttr(avg_node + ".automaticWeight", 0)
 avg_curve = cmds.duplicate(top_curve)[0]
 avg_curve_shape = cmds.listRelatives(avg_curve, c=1)[0]
 cmds.connectAttr(top_curve_shape + ".worldSpace[0]", avg_node + ".inputCurve1", force=1)
 cmds.connectAttr(bottom_curve_shape + ".worldSpace[0]", avg_node + ".inputCurve2", force=1)
 cmds.connectAttr(avg_node + ".outputCurve", avg_curve_shape + ".create", force=1)

 #Create duplicate mesh with inputs
 dup_mesh = mel.eval("polyDuplicateAndConnect;")[0]
 dup_top_edges = [x.replace(base_mesh, dup_mesh) for x in top_edges]
 dup_bottom_edges = [x.replace(base_mesh, dup_mesh) for x in bottom_edges]

 # Create wire deformer on duplicate mesh
 wire_deformer = mel.eval("wire -gw false -en 1.000000 -ce 0.000000 -li 0.000000 -w " + avg_curve + " " + dup_mesh +";")[0]
 base_wire = avg_curve + "BaseWire"
 base_wire_shape = cmds.listRelatives(base_wire, c=1)[0]
 cmds.connectAttr(avg_node + ".outputCurve", base_wire_shape + ".create", force=1)

 # Set wire deformer params
 cmds.setAttr( wire_deformer + ".scale[0]", 0)
 cmds.setAttr( wire_deformer + ".envelope", 1.2)
 editAttrs(wire_deformer, ["ce","te","li","ro","sc[0]"], l=1, k=0, cb=0)

 #Set weights

 #Zero all weights
 mel.eval("artAttrToolScript 4 \"" + wire_deformer +"\";")
 mel.eval("artAttrPaintOperation artAttrCtx Replace;")
 mel.eval("artAttrCtx -e -value 0 `currentCtx`;")
 mel.eval("artAttrCtx -e -clear `currentCtx`;")

 #Select vertices
 mel.eval("changeSelectMode -component;")
 mel.eval("hilite -r " + dup_mesh + " ;")
 mel.eval("setComponentPickMask \"Line\" true;")
 cmds.select(dup_top_edges, dup_bottom_edges)

 for i in range(selection_growth):

 #Set weights for region to 1
 mel.eval("artAttrValues artAttrContext;")

 mel.eval("artAttrToolScript 4 \"" + wire_deformer +"\";")
 mel.eval("artAttrPaintOperation artAttrCtx Replace;")
 mel.eval("artAttrCtx -e -value 1 `currentCtx`;")
 mel.eval("artAttrCtx -e -clear `currentCtx`;")

 #Smooth n times
 mel.eval("artAttrValues artAttrContext;")
 mel.eval("artAttrPaintOperation artAttrCtx Smooth;")

 for i in range(smoothing):
 mel.eval("artAttrCtx -e -clear `currentCtx`;")

 mel.eval("changeSelectMode -object;")

 # Group,rename all objects
 main_grp = cmds.group(em=1, w=1, n=resolveName("GRP_sticky"))
 cmds.setAttr(main_grp + ".visibility", 0)
 editAttrs(main_grp, ["tx","ty","tz","rx","ry","rz","sx","sy","sz"], l=1, k=0, cb=0)

 top_curve = cmds.rename(top_curve, resolveName("CRV_sticky_top"))
 bottom_curve = cmds.rename(bottom_curve, resolveName("CRV_sticky_bottom"))
 avg_curve = cmds.rename(avg_curve, resolveName("CRV_wire"))
 base_wire = cmds.rename(base_wire, resolveName(avg_curve + "BaseWire"))

 cmds.parent(top_curve, bottom_curve, avg_curve, base_wire, main_grp)
 if cmds.listRelatives(dup_mesh, p=1):
 cmds.parent(dup_mesh, w=1)

 wire_deformer = cmds.rename(wire_deformer, resolveName("stickyWire"))

Have fun!



Function createFollicle()

This function I wrote in python is very useful to create Follicles on surfaces and polygons, without the need of creating Hairs and then deleting the Hair System and the curves just to keep the follicles.
Plus, of course, is a good chance to code something and to have a function you can use in your scripts.

How does it works..

The function requires as parameters a list of 3 elements, named pos for a point position in 3d space and a nurbs surface OR a poly surface.

The follicle is located on the polygon/surface by finding the closest point on it from the given position.

I have included a second function, named createFollicles(), where you can pass an array of 3d points instead just one point. It will create multiple follicles.

Here’s a quick example..

I create a torus polygon, select some vertices, get their positions and create follicles on the vertices..

torus = cmds.polyTorus()
cmds.select(torus[0]+".vtx[10]", torus[0]+".vtx[20]", torus[0]+".vtx[30]")
sel = cmds.ls(sl=1, fl=1)

for obj in sel:
	pos = cmds.pointPosition(obj, w=1)
	follicle = createFollicle(pos, poly_surface = torus[0])
	print follicle

Here’s the function code:

import maya.cmds as cmds
import maya.OpenMaya as OpenMaya

def createFollicle (pos=[0, 0, 0], nurbs_surface=None, poly_surface=None):
	if (nurbs_surface==None and poly_surface==None):
		OpenMaya.displayError("Function createFollicle() needs a nurbs surface or poly surface")

	transform_node = cmds.createNode("transform")
	cmds.setAttr((transform_node +".tx"), pos[0])
	cmds.setAttr((transform_node +".ty"), pos[1])
	cmds.setAttr((transform_node +".tz"), pos[2])
	#make vector product nodes to get correct rotation of the transform node
	vector_product = cmds.createNode("vectorProduct")
	cmds.setAttr((vector_product+".operation"), 4)
	cmds.connectAttr( (transform_node+".worldMatrix"), (vector_product+".matrix"), f=1)
	cmds.connectAttr( (transform_node+".rotatePivot"), (vector_product+".input1"), f=1)

	#connect the correct position to a closest point on surface node created
	if nurbs_surface:
		closest_position = cmds.createNode("closestPointOnSurface", n=(transform_node+"_CPOS"))
		cmds.connectAttr( (nurbs_surface+".ws"), (closest_position+".is"), f=1)
		cmds.connectAttr( (vector_product+".output"), (closest_position+".inPosition"), f=1)
	if poly_surface:
		closest_position = cmds.createNode("closestPointOnMesh", n=(transform_node+"_CPOS"))
		cmds.connectAttr( (poly_surface+".outMesh"), (closest_position+".im"), f=1)
		cmds.connectAttr( (vector_product+".output"), (closest_position+".inPosition"), f=1)

	#create a follicle node and connect it
	follicle_transform = cmds.createNode("transform", n=(transform_node+"follicle"))
	follicle = cmds.createNode("follicle", n=(transform_node+"follicleShape"), p=follicle_transform)
	cmds.connectAttr((follicle+".outTranslate"), (follicle_transform+".translate"), f=1)
	cmds.connectAttr((follicle+".outRotate"), (follicle_transform+".rotate"), f=1)
	if nurbs_surface:
		cmds.connectAttr((nurbs_surface+".local"), (follicle+".is"), f=1)
		cmds.connectAttr((nurbs_surface+".worldMatrix[0]"), (follicle+".inputWorldMatrix"), f=1)
	if poly_surface:
		cmds.connectAttr((poly_surface+".outMesh"), (follicle+".inm"), f=1)
		cmds.connectAttr((poly_surface+".worldMatrix[0]"), (follicle+".inputWorldMatrix"), f=1)

	cmds.setAttr((follicle+".parameterU"), cmds.getAttr (closest_position+".parameterU"))
	cmds.setAttr((follicle+".parameterV"), cmds.getAttr (closest_position+".parameterV"))

	#return strings
	return [follicle_transform, follicle, closest_position]
def createFollicles  (follicle_positions=[[0,0,0]], nurbs_surface=None, poly_surface=None):


	if (nurbs_surface==None and poly_surface==None):
		OpenMaya.displayError("Function createFollicles() needs a nurbs surface or poly surface")
	for pos in follicle_positions:
		lst = createFollicle(pos, nurbs_surface, poly_surface)
	return out_follicles

Function resolveName()

When you call this function, it gives you back a unique name in the Maya scene. Very useful when creating objects programmatically and you want to keep unique names in your scene (Essential when dealing with maya scripts).

If the string passed as parameter is not assigned yet, then the function returns the string itself, else it will return the string plus “_#”.

Python code:

def resolveName(n):
	name = n
	if cmds.objExists(n):
		i = 1
		while cmds.objExists(str(n) + "_" + str(i)):
		name = str(n) + "_" + str(i)
		print "Warning: Object named " + n + " already exists. Used " + name + " instead."
	return name

MEL code:

proc string resolveName(string $n){
	string $name = $n;
	if (`objExists $n`){
		int $i = 1;
		while (`objExists ($n + "_" + string($i))`){
		$name = $n + "_" + string($i);
		print ("Warning: Object named " + $n + " already exists. Used " + $name + " instead.");
	return $name;


Suppose you want to create a series of objects using the same name. By calling the function before the name you don’t have to worry about naming conflicts:

import maya.cmds as cmds

num_spheres = 10
sphere_list = list()

for i in range(numspheres):
     new_sphere = cmds.sphere(n=resolveName("mySphere"))

This will create the spheres mySphere, mySphere_1, mySphere_2 and so on.
If you recall the function again, the numbering will start from the first available name.