A question about relative shape keys

Howdy,

I have a general question about shape keys. For example:

Suppose one has modeled a character, and then created some shape keys that affect the lip region - such as a smile and a wicked grin. Then one decides that one would like the character to have thicker lips – for all the shape keys. What I would like to do then is to just to modify the lip thickness of the Basis shape key - and then, as seen in the 3D window, all of the shape keys should (at least optionally) get thicker lips.

In fact, from reading the manuals
(for example http://wiki.blender.org/index.php/Manual/PartVIII/Shape_Keys)
I would have thought that this would happen automatically.
If the shape keys store differences (deltas) relative to the Basis shape key (when the Relative button is pressed in the Shapes panel) - then altering the Basis shape would automatically change the appearance of the other shapes in precisely the manner I want. But for some reason it does not work that way - at least not for me.
If I edit my Basis shape - nothing happens to the 3D apperance of the other shapes.

As far as I have bee able to figure out - there are two ways to partially achieve what I want (without having to manually fix all the shape keys):

  1. There is a possiblity in Blender to edit the Basis shape and then, with the edited vertices selected, one presses W->Propagate to all shapes.
    But this just sets the appearance of those particular vertices, for all the shape keys, to be identical to those of the Basis shape key. The relative “deltas” (of the vertices in question) for the shape keys are lost. This method works great if I want to give my character larger ears after I have defined the smile and the wicked grin shape keys. It does not, however, work for giving the character thicker lips while maintaining the general lip shape of the smile and the wicked grin.

  2. One can add a second Basis key “Basis2”, based on the original Basis key. One edits “Basis2” to get thicker lips. Then one cranks up the the Key value slider of Basis2 to 1 - and simsalabim - all of the shapes get thicker lips just like I wanted.
    But then , while fine tuning for example the smile shape key, the character still has thin lips. One would have to swap to object mode to see how it really looks,
    go back to edit mode and push the vertices etc.

The feature that I want seems like such a useful and (comparatievely) easily implemented feature that I find it hard to believe it is not already a part of Blender.
And in truth, from reading the manuals i get the impression that the behaviour I am after should be the default behaviour. So, am I missing something? Is there a way to make changes of the basis shape propagate in a relative manner to the other shapes?

Thankful for any help.

P.S I am running Blender 2.45 on OSX 10.4.11 on an Intel Macbook.

The feature that I want seems like such a useful and (comparatievely) easily implemented feature that I find it hard to believe it is not already a part of Blender.
Nor is it AFIAK a part of any of the “big boys” like Maya or Max etc .

All shapekeys (blendshapes, morph targets whatever flavor you want to call it) are simply a set of vector data that is calculated by taking the set of vertices in a base mesh and then taking the vertices in the new shapekey and then calculating the verts that have moved from the base shape and then calculating a vector from the base to the new shape for those verts that have moved . In other words, shapekeys are rather “static” after you create them and won’t up date if you just alter the base mesh because the vector data for the shapekey is already stored as the shape you made for the shapekey (thin lips even if you change the base to have thick lips) .

You can see this is the case if you open up an IPO curve editor and select “Shapes” for the curve type . And if you just Ctrl-LMB a few keys and even go beyond the 1.0 (full) influence limit you can see the the shapekey verts that were moved to make the shapekey move in a single noticable direction …

And so solution for the new feature you want is somewhat in your second point … You just need to add a new shapekey : “thick lips”, and key that to full influence (or more) when you want a thicker lips for your character and adjust the curves to get decent results … All of this most easily done using the IPO curve editor BTW, all the shapekeys show up on the side …

Though this can get somewhat complicated and hard to get right … The last time I tried the morphing faces effect, I used shapekeys for the face types and just used hooks to animate the expressions …

The issue is that Blender is doing exactly what you think it should do. For those verts that are in VGroup, they are moved into position as the key exerts influence. So if Key 1 has thin lips, as key 1 increases, the lips will thin out. If the verts are not part of VGroup, then they will not be moved for that key.

You could always write your own propagate script to do what you want.

Such a script is not yet possible. Vertex positions can’t be changed with scripts once a mesh has shape keys.

All you can do is append new shape keys.

it’s a reasonable request and it is something that IDEALLY, blender should get eventually.

older versions of maya suffer the same problem and so if you change something in the base shape, the target is calculating the DELTA so it REALLY SCREWS UP everything when you apply the morph target. but there have always been scripts from highend that will PROPAGATE the change in a base shape to its targets. and i believe this is out of the box functionality for maya 2008.

lightwave has had this ability to propagate sculpt changes that do not involve altering vertex order to its morph targets (endomorphs) since version 6.

this is a case where in an ideal working environment, nobody starts working on part D until parts A,B and C have been locked down and rendered IMMUTABLE.

this NEVER happens in production. and your request would allow flexibility in production pipeline.

if it’s not in a feature request as a result of ed or peach, it should be.

jin

@toloban: thanks for the info. Since it seems you have to add a new shape key, would this work?

If you have the set Basis{v,x,y,z} where v is the vertex, xyz is the location, and NewBasis{v,x,y,z}, where the NewBasis has some vertexes that are in a different location (thicker lips in this example). You then have OldSmile(v,x,y,z} you want to compute NewSmile{v,x,y,z} where:

Initialize NewSmile = OldSmile
for each v in {Basis}
NewSmile(v,x,y,z) += delta ( NewBasis(v,x,y,z), Basis(v,x,y,z) )
endfor
Add NewSmile on as a shape key

and delta is a function that compares the two X Y Z coordinates and returns the three differences, and += is a function that applies the X delta to the v,x, the Y to the v,y etc.

so you would have to select three existing: basis, newbasis (fat lips) and the key you want propagated.

Thanks a lot for the replies guys.

Vertex Pusher:
I would also have expected that the stored set of difference vectors for a shape key would be static. But then - if the 3D appearance of each shape key is generated by adding the shape key difference vectors to the positions of the vertices of the Basis mesh - then the appeareance of that key shape would not be static. Changing the Basis
would alter the 3D-appearance of the shape key in question while leaving the stored set of difference vectors unaltered (static).

But this is not how it works in practise. So how does it work?
Maybe as one alters the Basis shape, the numerical sets of difference vectors for all shapes is altered in such a way that the 3D appearance of all these shapes remain unaltered. This would be consistent with the way Blender works.

Another option is that the shape keys are stored as absolute positions of the vertices - and then the difference vectors are simply calculated (and maybe stored) as an intermediate step to get the proper (differential) response to settings of the shape key values. This would also be consistent with the way Blender works - but it would go against what I have seen written on this topic.

Papasmurf:
+1 on your question to toloban. And where would one find documentation about reading shape key data in python, and appending shape keys?

All:
While it would be nice with a python script for this feature, it would be even nicer if the feature was part of the source. For example one could have a checkbox for every shape in the shapes panel. When checked, the shape key inherites
changes from the Basis in the manner that I suggested previously. When unchecked, the appearance of the shape key is not altered by changes in the Basis. Having the checkbox unchecked as default - this setup would be backwards compatible with earlier versions.

One can also play with the idea of having an even more relative optional mode.
Because, while the above setup would work fairly well for the thickened lips example, there are other cases where it would not work well. For example, suppose that we want to alter our character’s vertitical forehead to a sloping forehead. Changing the forehead slope in the Basis key, the shape key for the raised eybrows will be screwed up no matter which of the above two options we choose (in one case the forehead for that key remains upright, and in the other the creases formed in the forhead will start traveling away from the forhead in the z-direction).
What one could possibly do is to express the difference vector of the shape keys relative to the Basis, not with respect to the objects x,y and z coordinates, but with respect to a local coordinate system attached to the particular vertex and its surrounding vertixes. Leaving it a little bit vague: if the surrounding of a a particular vertex is rotated in the Basis shape - the difference vector will also automatically be rotated (as expressed in the object coordinates). This kind of set up could work well for the example of the sloping forehead/raised eybrows.

On the other hand, if one wants a more sloping forehead, for all shape keys - maybe the best option is to simply use the new Mesh Modifier feature that is already in the Blender 2.45.15. That would probably work well.

Papasmurf and everybody. I realized it is possible to do a relative propagation script, I am sorry I said it is not possible. A few workarounds make it possible to overcome the limitations of Python access to the data.

Vertices can’t be modified using Python once the mesh has shapes, I tested this an it still hasn’t changed, using NMesh or Mesh modules.

I have one such script of my own. Not user friendly, but that doesn’t mean an easy to use script can’t be made.

The trick to create new shape keys using non absolute geometry is to generate a copy of the object (or the new base object) and all its new keys from scratch. This implies that all materials and modifiers should be copied to the new object. The only part which is tricky is the drivers. All drivers should be defined as Pydrivers to be copiable.

In fact CJD from Plumíferos once posted a (buggy) script which copies all the shape keys from one object to another one. They get copied as absolute positions. Making it relative implies a little modification. I used his script to find the errors in mine, which works as complement to a modified version of the old Makehuman, using my own human mesh.

oh, that sounds very cool. So you could have a male head, and copy it and sculpt to be a female head. Shape key the male, and propagate to the female. Or in the plumiferos world, shape key a sparrow as a base mesh, and you (can) have the keys for any base mesh deviant.Or as this OP, thinlips -> fat lips.

I had this script somewhere in my hard disk, here it is adapted to Blender 2.44+. It copies shape keys from active to inactive (selected) objects:

#!BPY
"""
Name: 'Copy difference shape keys'
Blender: 244
Group: 'Object'
Tooltip: 'Copy shape keys from active to inactive object, using differences'
"""

import Blender

def copykeys():
    Blender.Window.EditMode(0)

    obs = Blender.Object.GetSelected()
    if len (obs)!=2:
        Blender.Draw.PupMenu("Error%t|Select two mesh objects")
        return

    #Copy from active to inactive
    mdest = obs[1].getData(mesh=1)
    msrc = obs[0].getData(mesh=1)

    if len(msrc.verts) != len(mdest.verts):
        Blender.Draw.PupMenu('Error%t|Mesh vertex count mismatch')
        return

    if msrc.key:
        srcvdata = msrc.key.blocks[0].getData()
    else:
        srcvdata = [v.co for v in msrc.verts]

    print "Source object: %s, target object: %s"%(obs[0].name, obs[1].name)

    #Target object vertices
    if mdest.key:
        #Use Basis key if it exists.
        destvlist = mdest.key.blocks[0].getData()
    else:
        destvlist = [v.co for v in mdest.verts]

    #List vertex differences.
    diffs = []
    for i in range(len(destvlist)):
        #Save difference vector.
        diffs.append (destvlist[i] - srcvdata[i])

    #Test whether source object has shape keys.
    if msrc.key:
        if not mdest.key:
            #Add basis
            mdest.insertKey()

        if msrc.key.ipo:
            if not mdest.key.ipo:
                mdest.key.ipo = Blender.Ipo.New('Key','keyipo')

        for block in msrc.key.blocks[1:]:
            print block.name

            vdata = block.getData()
            for i in range(len(msrc.verts)):
                mdest.verts[i].co.x = vdata[i].x + diffs[i].x
                mdest.verts[i].co.y = vdata[i].y + diffs[i].y
                mdest.verts[i].co.z = vdata[i].z + diffs[i].z

            mdest.insertKey()

            if msrc.key.ipo:
                curve_ipo = msrc.key.ipo.getCurve(block.name)
                if curve_ipo:
                    curve_new = mdest.key.ipo.addCurve('Key %d'%(len(mdest.key.blocks)-1))
                    curve_new.extend = curve_ipo.extend
                    curve_new.interpolation = curve_ipo.interpolation
                    if curve_ipo.driver:
                        curve_new.driver = curve_ipo.driver
                        curve_new.driverObject = curve_ipo.driverObject
                        curve_new.driverChannel = curve_ipo.driverChannel
                        if curve_new.driverExpression:
                            curve_new.driverExpression = curve_ipo.driverExpression

                    for nurbs in curve_ipo.bezierPoints:
                        curve_new.append(nurbs)

            mdest.key.blocks[-1].name = block.name

    else:
        Blender.Draw.PupMenu('Error%t|No shape keys in source mesh, inverted order?')
        return

copykeys()

Toloban, thanks a lot for posting your script!

It was very helpful. Not only could it be used to do what I wanted, but more importantly it illustrated the proper syntax of dealing with vertex keys in bpython.

Fiddling around with the script I realised that one can in fact reposition the vertices of existing shape keys after all. So I wrote a script that is specifically designed to propagate changes of the basis key to other keys, as discussed previously in this thread, Here us how to use it:

  • Starting from the “Basis” key create a new key and name it “NewBasis”.
  • Edit the “NewBasis” key to get thicker lips or whatever you are after.
  • In object mode - select only the object (character) in question.
  • Load the script into blender’s text editor and with the mouse over the text editor - click Alt-P

If all works well a little pop-up window says “Updated Shape Keys”. Note that the script yields an error if there are fewer than three shape keys total in the object, since then there is no point to the script.

What the script does is that it simply adds the difference vectors between the “NewBasis” and the “Basis” key to all shape keys, including the “Basis” key but excluding the “NewBasis” key. Here is the code:


#!BPY
"""
Name: 'RelativePropagate'
Blender: 245
Group: 'Object'
Tooltip: 'Apply vertex differences between a key called NewBasis and the Basis key to all shape keys of a selected object'
"""

import Blender
Blender.Window.EditMode(0)
temp = Blender.Object.GetSelected()

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

ob=temp[0].getData(mesh=1)

if not ob.key:
	Blender.Draw.PupMenu("Error: Object has no keys")
else:
	if len(ob.key.blocks)<3:
		Blender.Draw.PupMenu("Error: No point of using this script unless you have at least three shape keys")

#Search for the "NewBasis" key

newbasisindex=[];i=0
for block in ob.key.blocks[1:]:
	i=i+1
	if block.name=="NewBasis":
		newbasisindex=i

if newbasisindex==[]:
	Blender.Draw.PupMenu("Error: No key named 'NewBasis'")

#Form list of vector differences between NewBasis and Basis

basisverts   = ob.key.blocks[0].getData()
newbasisverts= ob.key.blocks[newbasisindex].getData()

delta_list=[]
for i in range(len(basisverts)):
	delta_vector=newbasisverts[i]-basisverts[i]
	delta_list.append(delta_vector)

#Add difference to all keys except NewBasis

i=-1
for block in ob.key.blocks[0:]:
	i=i+1
	keyverts = block.getData()
	
	if not i==newbasisindex:
		for j in range(len(ob.verts)):
			keyverts[j].x=keyverts[j].x+delta_list[j].x
			keyverts[j].y=keyverts[j].y+delta_list[j].y
			keyverts[j].z=keyverts[j].z+delta_list[j].z

Blender.Draw.PupMenu("Updated Shape Keys")

If one has a male face with shape keys for various facial expressions one could try to create a new key “NewBasis” starting from the “Basis” key, and move vertices around to make it look female. Then one can run the script to, hopefully, get all the shape keys to look as more or less realistic female expresssions. It may of course be that one needs to make adjustments to the various shape keys afterwards. Note that one can preview the effect of the script even before applying it by setting the key value slider for “NewBasis” to 1.

The script may work reasonably well for small changes of the basis. However, if we want to completely transform the basis shape key, maybe from human into some alien form, then the resulting shape keys will probably not be useable. One can however imagine a more refined script that will be able to deal even with large changes of the basis shape - to a certain degree. I may post such a script at some point.

yea! Let meta-androcto know about this so we can get it added to the library.i will reference this thread in the wiki.

No Rickard, THANK YOU! Really. You made me learn something that will make my workflow easier. Besides, it makes me happy when someone wants to learn Python to solve problems.

You are right, there is no need to use two objects.

I had not realized shape key blocks are now writable from Python. I should read the documentation more often. They were read-only last time I checked. Now I know that the mesh data is writable if I use the shape key blocks instead of the regular vertex data if the mesh has shape keys.

I hope you don’t mind if I post a modified version. The active shape key is the one from which you take the new data, so there is no need to give the shape key special name. Maybe other people should give their opinion on what they think is easier.

#!BPY

"""
Name: "Relative Propagate"
Blender: 245
Group: "Mesh"
Tooltip: 'Apply vertex differences between the active key and the Basis key to all shape keys of a selected object'
"""

import Blender

def propagate():
    edit_mode = Blender.Window.EditMode()
    Blender.Window.EditMode(0)

    # Recommended to use "active" instead of "selected" object when there is only one object selected.
    scene = Blender.Scene.GetCurrent()
    active_object = scene.objects.active

    if active_object.type != "Mesh":
        Blender.Draw.PupMenu("Error: Object must be a mesh")
        #Fatal error, so quit.
        return

    # Mesh data from object
    ob_mesh = active_object.getData(mesh=1)

    if not ob_mesh.key:
        Blender.Draw.PupMenu("Error: Object has no keys")
        return

    if len(ob_mesh.key.blocks)<3:
        Blender.Draw.PupMenu("Error: No point of using this script unless you have at least three shape keys")
        return

    basis_verts = ob_mesh.key.blocks[0].getData()

    # Use active shape key as new basis.
    new_basis_index = active_object.activeShape - 1
    new_basis = ob_mesh.key.blocks[new_basis_index]
    new_basis_verts= new_basis.getData()

    # Form list of vector differences between new_basis and Basis
    # A tolerance could be used to ignore all vertices in the same location,
    # if that made a real speed difference.
    delta_list=[new_basis_verts[i] - basis_verts[i] for i in range(len(basis_verts))]

    for block in ob_mesh.key.blocks:
        # Ignore reference shape key.
        if block != new_basis:

            key_verts = block.getData()

            for j in range(len(basis_verts)):
                key_verts[j].x=key_verts[j].x+delta_list[j].x
                key_verts[j].y=key_verts[j].y+delta_list[j].y
                key_verts[j].z=key_verts[j].z+delta_list[j].z

    Blender.Draw.PupMenu("Updated Shape Keys")
    Blender.Window.EditMode(edit_mode)

propagate()

For anyone not comfortable messing with Python, I believe the solution was almost touched on in the first post.
Make a second “basis” key with the thicker lips.
Keyframe this shape.
Choose the Grin shape then Add New Shape
Call it GrinThick or something.

Replace each shape the same way then delete the originals.

It’s fiddly but I think this does what’s wanted.

Of course, if you’ve already started animating, this will be painful since all new shapes will need keying from scratch.

Toloban: Glad we could help each other

AndyD: You are right! For some reason I assumed that when creating a new shape key it would always be based on a single vertex key - not a blend of different vertex keys. So if one only does as you propose and set the key value slider for NewBasis and Grin to 1 before clicking the add new shape key - one will automatically get a shape with a thick grin. Repeating for all shapekeys does the trick. Neat.

I may still get back concerning that more refined script though:-)