Script to mirror mesh with shape keys

Howdy,

For an object that has shape keys, pressing the “Apply” button (at least in Blender 2.492) for a mirror modifier yields

“Modifier cannot be applied to Mesh with Shape Keys”

To the best of my knowledge nobody has posted a script to overcomes this.
So, I have written a script that X-mirrors a half-mesh with shape keys.
Here is the script:


#!BPY
"""
Name: 'MakeMirrorShape'
Blender: 249.2
Group: 'Object'
Tooltip: 'This script mirrors (about x=0) a mesh with shape keys.
	For shape keys with name endings like "_R", ".Left" etc.,
	corresponding mirrored assymetric shape keys will be added.
	For other shape keys, mirror symmetric shape keys will be formed.
	Note that this script is not intended for a full mesh -
	it is supposed to be applied to one half of a mesh.
"""

import math
from math import *
import Blender
from Blender import *
from Blender.Mathutils import *

Blender.Window.EditMode(0)
oblist=Blender.Object.GetSelected()

if len(oblist)!=1:
	Blender.Draw.PupMenu("Error: Select a single object.")

object=oblist[0]
shapeIndex=object.activeShape
me=object.getData(mesh=1)

if not me.key:
	Blender.Draw.PupMenu("Note: Object has no keys - you could just use the mirror modifier and apply this.")
else:
	if len(me.key.blocks)<2:
		Blender.Draw.PupMenu("Note: Object has only one key - you could delete the key and apply a mirror modifier.")

######################################################################################
# Defining a function to right-left flip name string endings

def flipName(inString):
	
	ns=len(inString)
	endingsL=['L','l','Left','left']
	endingsR=['R','r','Right','right']
	indexlist=[1,0];
	endings=[endingsL,endingsR]
	preendings=['.','_','-']
	
	hitIndex=[]
	for i in range(0,2):
		for j in range(len(endings[i])):
			e=endings[i][j]
			if len(e)<=(ns-1):
				if e==inString[ns-len(e):ns]:
					for pe in preendings:
						if inString[ns-len(e)-1]==pe:
							firstPart=inString[0:ns-len(e)-1]
							outString=firstPart+pe+endings[indexlist[i]][j]
							return outString
								
	return inString

######################################################################################
# Defining a function to check if a string is in a list 

def inList(string,stringlist):
	for lstring in stringlist:
		if string==lstring:
			return True
	return False

######################################################################################
# Adds mirrored vertices. Coordinates may be adjusted later

print 'Adding vertices'

epsilon=0.0001 	#The tolerance for considering an index to be at x=0
n=len(me.verts)
mirrorIndexList=[] 
j=n-1	 

basisverts=me.key.blocks[0].getData()[:]
bverts=[]
for i in range(len(basisverts)):
	bverts.append(basisverts[i]*1)	#Really important to multiply by 1, otherwise bverts just links to the key data
						#and these links get broken, giving seemingly random results, as one adds vertices. 

vertseq=[]

for i in range(n):	

	ivert=bverts[i]
	xvec=ivert.x
	yvec=ivert.y
	zvec=ivert.z	

	if -epsilon<xvec<epsilon:
		mirrorIndexList.append(i)  #Makes the original index point to itself
	else:
		j=j+1
		mirrorIndexList.append(j)
		vertseq.append((-xvec,yvec,zvec))

me.verts.extend(vertseq)

######################################################################################
# Create symmetric and asymmetric shape keys

print 'Setting up the shape keys'

km=len(me.key.blocks)

# Making a list of key shape names - to avoid adding shapes with existing names

nameList=[]
for block in me.key.blocks[1:km]:
	nameList.append(block.name)
	
# Here is the main loop
	
for block in me.key.blocks[1:km]:
	
	keyverts=block.getData()
	name=block.name
	newname=flipName(name)
	
	if newname!=name and inList(newname,nameList):
		Blender.Draw.PupMenu("Note:Mirror named shape key already exists - not creating new shape key")
	else:			
		if newname==name:
			#For a symmetric key
			mirrkeyverts=keyverts
		else:
			#For an asymmetric key
			object.insertShapeKey()
			newblock=me.key.blocks[-1]
			newblock.name=newname
			mirrkeyverts=newblock.getData()
			for j in range(n):
				#Comment: If the added ShapeKey was based on 'Basis' - this little loop could be omitted
				mirrkeyverts[j].x=bverts[j].x 
				mirrkeyverts[j].y=bverts[j].y
				mirrkeyverts[j].z=bverts[j].z
		
		for j in range(n):
			mj=mirrorIndexList[j]
			mirrkeyverts[mj].x=-keyverts[j].x 
			mirrkeyverts[mj].y=keyverts[j].y
			mirrkeyverts[mj].z=keyverts[j].z


######################################################################################	
# Create mirrored vertexgroups and vertex weights 

print 'Mirroring vertex groups and vertex weights'

GroupList=me.getVertGroupNames()

for name in GroupList:
	list=me.getVertsFromGroup(name,1)	#The argument 1 is a flag to get weight values as well
	newname=flipName(name)
	
	if inList(newname,GroupList) and newname!=name:
		print 'Mirror named group already exists - not creating new group'
	else:
		if newname!=name:
			me.addVertGroup(newname)
		for vtuplet in list:
			index=vtuplet[0]
			if not (newname==name and mirrorIndexList[index]==index):
				me.assignVertsToGroup(newname,[mirrorIndexList[index]],vtuplet[1],1) 

######################################################################################
# Create mirror edges and crease values

print 'Mirroring edges'

m=len(me.edges)
edgeseq=[]		
mirrEdgeList=[]

for k in range(m):
	iedge=me.edges[k]
	edge=[iedge.v1.index,iedge.v2.index]
	mirredge=[mirrorIndexList[edge[0]], mirrorIndexList[edge[1]]]
	
	if not (mirredge[0]==edge[0] and mirredge[1]==edge[1]):
		mirrEdgeList.append(j)
		edgeseq.append(mirredge)
	else:
		mirrEdgeList.append(k)

me.edges.extend(edgeseq)

for k in range(m):
	if mirrEdgeList[k]!=k:
		me.edges[mirrEdgeList[k]].crease=me.edges[k].crease

######################################################################################a
# Create mirrored faces, material indices, smoothness and normals

print 'Mirroring faces'

m=len(me.faces)
faceseq=[]
faceIndexList=[]	#A pointer list. Position i in the list will contain the index of the face that face number i is mirrored to.

for k in range(m):
	iface=me.faces[k]
	vertlist=iface.verts	
	
	#Setting up a list of the indices for the mirrored face
	mirrorFaceIndexList=[]		#List of lists with indices for the added faces
	centercount=0
	for vert in vertlist:
		vi=vert.index
		mi=mirrorIndexList[vi]
		if mi==vi:
			centercount+=1
		
		mirrorFaceIndexList.append(mi)
	
	if centercount==len(mirrorFaceIndexList):
		faceIndexList.append(k)
	else:
		faceseq.append(mirrorFaceIndexList)
		faceIndexList.append(m+len(faceseq)-1)
		
me.faces.extend(faceseq)

for k in range(m):
	
	ki=faceIndexList[k]
	if not ki==k:
		#Mirroring the material index
		me.faces[ki].mat=me.faces[k].mat
		
		#Mirroring smoothness
		me.faces[ki].smooth=me.faces[k].smooth
		
		#Making a selection for subsequently flipping the normals
		me.faces[k].sel=0
		me.faces[ki].sel=1

me.flipNormals() #Flips normals of selected faces

######################################################################################

object.activeShape=shapeIndex
Blender.Redraw()
print 'Done'
Blender.Draw.PupMenu("Mirrored Shape Keys")

How a particular existing shape key is copied onto the mirrored side depends on
the name ending of the particular shape key. For example, for name endings of the type

.Left
.right
_L

a new left-right flipped asymmetric shape key is created with a corresponding
flipped name. For example if you have a shapekey for the closing of the left eye
called “Blink_L”, then a new shape key for the closing of the right eye called
“Blink_R” is created.

If there is no Left-Right indication of the type recognized by the script, a new
shape key is not added - but the shape key in question becomes symmetric.

Besides the shape keys, the script mirrors some other common mesh properties

edges, edge creases
faces, material indices, smothness and normals
vertex groups and vertex weights

There are other properties that are not mirrored, like UV-mapping.
That can probably be added if needed.

I have not done very extensive checking of the script - but for
the purposes I have used it for, it works like a charm.

Note that this script is not intended to copy shape keys from
left to right in an already existing full mesh. That requires a different script.
Such a script, or inbuilt feature, seems very useful,
but thus far I have however only been able to find a dead link to such script here:
http://blenderartists.org/forum/showthread.php?t=57643
If you know of a live link to such a script, or general feature for working with symmetric
shape keys on existing full meshes, please direct me to it.

Anyway, I hope those who have added shape keys before applying the mirror modifier finds the script above useful.

Best,

/Rickard