Improving shape key tools

I’ve noticed recently that Blender now has support for transferring UV coordinates, normals, as well as weights. I’ve long felt that shape keys could use more features in Blender, and I’ve begun studying programming to try to help myself make more tools.

In any case, I don’t know if this is the right place for it for sure or not, but I’ll list some of the things I plan on working on in the future. If anyone has ideas on how to implement them or general advice(or they’re existing features and I just don’t know it…), I’d appreciate it if you let me hear it.

  1. Transferring shape keys from one mesh to another regardless of topology
    –SoftImage XSI has something like this in its Gator feature. It’s not perfect, but for a large number of situations it works. I believe I know a number of things that could make the feature even better than it is in SoftImage, like more falloff control options.

  2. Transfer of shape keys based on vertex index and position; any vertex whose position and index don’t match is skipped

  3. Transfer of shape keys based solely on comparing vertex position; this alone would solve the problem of accidentally doing things like deleting a vertex and as a result losing all shape key compatibility

  4. Related to this, possibly more intelligent use of vertex indices or index reorganizing based on vertex proximity. However, with the above features, this may not be necessary

  5. Possibly having a modifier that operates somehow with vertex groups in order to do some of the above more effectively

  6. Select all vertices modified by shape key(and/or vice versa), with a falloff

  7. Ideally, some way to make bones follow large shifts in shape keys(i.e. moving the knee up and down with a shape key and having the bone follow that)

  8. Resolve the issue of shape keys not being applicable with(some) modifiers; there are many modifiers for which there’s almost no reason at all they shouldn’t be applicable with shape keys, even subdivision surface. Using Oscur Art Tools I apply subD to models with shape keys several times almost every day, it should be easy to automate.

  9. If it’s not there already something great would be an equivalent to Zbrush’s morph brush, either for sculpting or in some other way.

At any rate, I’m not asking anyone to do these but more for anyone interested to look over it and if they have an idea of how to do it efficiently, tell me about it. I’m planning to do these myself at some point. Transferring shape keys based on spatial analysis seems like the hardest of the bunch, but even then as long as I can figure out ray casting in python and methods for finding the closest vertices, I think it’s doable even for a beginner like myself, although I’m not sure how to go about making it speedy/efficient. I’m fairly sure I’ve had other ideas in the past as well; I’ll either edit this post or post again if I remember some others.

Sorry if these are features existing in Blender already, are a topic of popular discussion already, or if I’ve chosen the wrong place to post. I may post some code I’ve written later since right now this is all just talk.

In Maya. you can transfert arrtibutes like vertex position based on Uvs or topology, or Uvs based on topology
In Houdini. you can reorder vertices like another model wih the same tpology

  1. you could probably hack together a batch process that would work ok for most cases using shrinkwrap here (which would basically end up being a naive raycasting approach), but anything more sophisticated is going to be quite hard.

  2. This wouldn’t be too hard, just a single loop through source verts. I assume by position you mean basis position. You can already transfer shapes based on vert index, but only if the number of verts match.

  3. there are a few edge cases you will need to consider, particularly when vertex distribution/topology is very different, when pairing verts. But you could make something that would work fine for topologically identical meshes that have different vert orderings without too much trouble.

  4. similar issues to 3. As a way of recovering vertex ordering that got mucked up by .obj i/o and not clicking preserve vert order, this would be fairly useful, but could quickly break down when meshes become more and more different.

  5. not sure how vert groups are going to help with the above, also unless the modifier is only creating shapekeys on the apply as shape button, this won’t play nice with the current shapekey implementation (shapekeys are evaluated at the start of the modifier stack, as a hidden modifier)

  6. I think it is possible to do this already

  7. Depending on what you want, this would be tricky. I am also not really sure what the point is, as the combined deformation would get quite weird. Normally the workflow would be pose with bone, then sculpt a corrective shape and control it with a driver.

  8. If it is just shapekeys also getting updated with a modifier’s effects when the modifier is applied, this should not be too hard to create a batch operation to do this, which it sounds like the oscurart tool is doing? You just loop through the mesh and generate a new mesh for each shape with the modifier applied, and then merge them into one final mesh.

  9. would be cool, but probably more useful with a proper sculpt layer implementation.

Most of this stuff should be doable as single operators in python, the only things that won’t are creating new types of modifier or changing the actual behaviour of modifiers, that would require C.

I did succeed in making a script which transfers shape keys by position, but as I expected and mentioned in the original post, I’m a little lost as to how to make it more efficient. For a model with 100k+ vertices it begins to work very slowly for even one shape key.

To begin, it operates by selecting a model, then selecting another model, selecting a desired shape key to transfer, and then running the script. The script compares the active and selected object, and then transfers the data of the active shape key for vertices with equivalent position.

At the very least, it works, but as I said it can become very slow with higher vertex counts, probably virtually unusable with high vertex and/or shape key count.

As to how it functions and why it’s slow, currently it simply creates a list, in index order, of model A’s vertices in the basis shape, and then creates a list of model B’s in the basis shape.

For every vertex in model B, it checks to see if there is a vertex in model A that shares its position, and if it does, it transfers the active shape key data from model A(active object)'s vertex to model B(selected object)'s vertex.
Of course, in the end, this results in comparing a lot of vertices.

I haven’t done much yet, but perhaps someone smarter/better at efficiency theory than me could help. My only guesses for what to do right now is to split the model into smaller positional groups(program-side, not literally) so that not every vertex is compared with every other vertex. I’m sure my way of doing it now is borderline idiotic but I’m not sure where to go in a way that I can sit down and correct the program right now.

I did have one alternative idea for a weaker sort of method, which would be to create a model whose vertex indices are reorganized based on their position, possibly with some assumptions. Such as, assume we added a part to model B; model B would still contain the same vertices as A, so if we simply reorganized the vertices of model B to match model A’s and then appended the new part afterwards, B would be capable of receiving morphs from model A(via python, of course; I’m not sure why Blender won’t allow you to transfer shape keys unless model vertex count matches, sometimes the transfer is valid).

Later tonight or tomorrow I plan on writing the script to select vertices based on whether they’re modified by a shape key or not, and include code to modify the selection based on the degree of change, as well as invert that rule(so that vertices with with less influence are selected instead).

Sorry for the long post but finally, there was one idea I remembered:
10. A cross-model “blend from shape” function, preferably capable of shape key transfer via both vertex position and vertex index

Thank you for the detailed reply, it’s much appreciated. I’m a little busy at the moment but I’ll read over your suggestions more fully later, if nobody else posts then I’ll just edit this one if I have comments/questions.

One thing that caught my eye though was your last line about requiring C. I’ve been studying C++ as well as a number of other languages since the beginning of this year, but I was under the impression that the lowest language level required for(most major) tasks in Blender was C++. Is that wrong? I’ll learn C if I have to but I was hoping I could get by in the modern area of graphic design with C++(and I’m finding C++ significantly harder to learn by self-study as I have very little to apply it to as of yet that holds my interest).

Also, just to anyone reading I’ll apologize if I’ve come off as a little arrogant or something. It certainly wasn’t my intent and I’m only trying to help add features, I suppose I was just trying to be succinct, to the point, and possibly a little too emphatic.

Ah, one more thing: I read in the new Blender update for the data transfer modifier that most things are transferrable but shape keys weren’t transferrable “yet.” “Yet?” Is this in the works? If so I would really like some confirmation, I’d rather not work on a feature overlapping with something the Blender official devs are working on.

I don’t think you come off as particularly arrogant.

If you want to know what the developers are working on, I suggest asking in IRC (#blendercoders at freenode).

C is more or less a subset of C++ and depending on how you have started learning C++, you have probably been learning the C parts first. Picking up C from C++ shouldn’t be hard at all.

For your position matching, are you storing a list of corresponding vertex indicies before doing the transfer, or are you re-calculating the nearest each time? The optimal way would be to create a list of the corresponding source vertex for each target vertex and then for shape key, move each target vertex to the corresponding source vertex position. If you want more detailed help, you could post code snippets here for review.

  1. There are indeed quite a lot of papers out there on this that we could consider using.

  2. This is what the “Transfer Shape Key” tool (properties panel dropdown) or the Blend from Shape/Propagate Shape tools do. The main limitation with these is that they require the exact same number of vertices, or else they refuse to budge.

  3. I made my own version. See http://aligorith.blogspot.co.nz/2011/04/rigging-tool-wip-transferring-shape.html - The linked script should still be the latest version, though I haven’t updated this in a few years since it still seemed to work last time I checked/needed it. It takes into account nearby vertices that are connected to the ones which haven’t changed, and interpolates those accordingly. See also my later post which describes some other optimisations I did to get it working even faster.

  4. This is another thing that my tool did (or actually, a second operator I included, for debugging purposes to see which verts it managed to map)

  5. There are obviously some dependency-order issues with trying to do that :wink:

Maybe I’ll think about it when I get around to that point… I think I’ve still got ground to cover before being able to tackle that, at least efficiently.

C is more or less a subset of C++ and depending on how you have started learning C++, you have probably been learning the C parts first. Picking up C from C++ shouldn’t be hard at all.

Huh, that’s good to hear, although I’m still a little surprised because I specifically remember hearing that C++ was the language that was used for lower level programming in Blender. Not that I’m doubting you.

For your position matching, are you storing a list of corresponding vertex indicies before doing the transfer, or are you re-calculating the nearest each time?

Well, I don’t remember exactly what I did at this moment, but I know it involved a lot of list iteration and right now I’m almost positive that use of dictionaries would be much more efficient than what I was doing, as apparently from a computational perspective if you only know an identifier of an item but not the information you want for it(in this case, the corresponding vertex index in the active object) then it’s going to be much faster using a dictionary than iterating through a list. Probably common sense to most programmers…

I’ll rework it a little later, most likely, and my plan now is to create a list of vertices of the active object, then create a dictionary by comparing the selected object to the active, with the format {selected index: matching active index}. After that, I’ll transfer the morph by creating a new shape key on the selected object and then iterating through the shape’s vertex data using the dictionary to determine what vertices to copy coordinates from from the active object’s shape.

It still might be slow, but it should be faster? Would there be a better way to do it? When I write code for it, I’ll post it. If nothing else it’ll be good practice for me in building dictionaries, I guess.

If you want more detailed help, you could post code snippets here for review.

Yeah, I’ve wanted to do that, I’ve just yet to produce something I could really post beyond experiments to figure out where to go. I’m switching through a bunch of different studies, so it takes me some time.

Responding a bit to your earlier post,

  1. there are a few edge cases you will need to consider, particularly when vertex distribution/topology is very different, when pairing verts. But you could make something that would work fine for topologically identical meshes that have different vert orderings without too much trouble.

That’s my primary concern. Not really so much to make a tool that will work in all situations, but just to improve the situation, since right now it’s pretty terrible, especially for newcomers, at least in my opinion. I use shape keys extensively, and I learned Blender by myself. Most of the techniques I know today I had to think of myself or scour the internet for(it was pure chance I found OscurArt Tools), and my biggest memory of it all is the amount of times I ended up making some extremely minor error and completely losing shape keys or shape key compatibility between two models.

As of right now, the reason I want some of these things is because I generally model in Blender to export for use in other programs, and this often involves splitting the meshes. Regrettably, this means that once I export from Blender, I can no longer create new shapes in Blender, export them, and simply add them to the exported model in an external program, because the vertex count and index order change when the model is split on its UVs. Using this technique I would be able to do that as well, as I can export shape keys to CSV files which I can use to load the shape keys inside of other programs without actually having to re-export the model.

  1. not sure how vert groups are going to help with the above, also unless the modifier is only creating shapekeys on the apply as shape button, this won’t play nice with the current shapekey implementation (shapekeys are evaluated at the start of the modifier stack, as a hidden modifier)

I’m not sure if I am either anymore… Don’t know what I was thinking. I know that I was thinking that if you were able to control shape keys with vertex groups like you can with many modifiers, then you would be able to have an approach to shape keys with a little more creative freedom/in a painting style using weight paint mode. In fact, that would be a little bit like Zbrush’s morph brush right there.

  1. I think it is possible to do this already

If it is, I still don’t know how to do it, much less with a falloff. I just looked again and I still can’t find an option for it.

  1. Depending on what you want, this would be tricky. I am also not really sure what the point is, as the combined deformation would get quite weird. Normally the workflow would be pose with bone, then sculpt a corrective shape and control it with a driver.

I can see why it might seem pointless to some people. I’m mostly talking about models with customization options. However, I don’t know very much about drivers, to be honest, and what I’m talking about may be accomplishable with drivers. I’m not sure.

What I meant was, I often find an issue when designing morphs/shape keys where, say, I make the arms shorter or longer. As a result, the location of, say, the elbow is going to change, and in order to accomodate this, you’re going to have to change the rest position of the bones that control the arms. One way to accomplish this would be to simply choose a vertex to hook a bone’s rest position to, I suppose.

At any rate I don’t really consider this one to be all that feasible except in the most broad of use cases.

  1. If it is just shapekeys also getting updated with a modifier’s effects when the modifier is applied, this should not be too hard to create a batch operation to do this, which it sounds like the oscurart tool is doing? You just loop through the mesh and generate a new mesh for each shape with the modifier applied, and then merge them into one final mesh.

Yes, that’s exactly it. I do plan to create it, in fact it might be the next thing I do. It certainly is a pain if you work with shape keys a lot/a lot of shape keys, even with something like OscurArt Tools, although it’s not that bad with it.

That’s good to hear. I’m really worried it’ll probably just end up being too complex for me, but I’m going to give it a shot sometime unless the Blender devs are working on it. I’m not much of a programmer or mathematician, so that’s where my lack of confidence lies.

  1. This is what the “Transfer Shape Key” tool (properties panel dropdown) or the Blend from Shape/Propagate Shape tools do. The main limitation with these is that they require the exact same number of vertices, or else they refuse to budge.

Maybe I wasn’t clear, but those limitations are what I intend to develop an alternative for. There is also another major “soft” limitation in that the order of the vertices in the index list also has to be the same. If for any reason the vertex order changes, often even by 1, the shape key is often completely destroyed. This introduces a number of limitations, and especially pitfalls for newer users who aren’t so used to 3d software yet.

Transferring based on matching position may end up being much slower but it should provide a sometimes more powerful alternative to the standard index-based method.

  1. I made my own version. See http://aligorith.blogspot.co.nz/2011…ing-shape.html - The linked script should still be the latest version, though I haven’t updated this in a few years since it still seemed to work last time I checked/needed it. It takes into account nearby vertices that are connected to the ones which haven’t changed, and interpolates those accordingly. See also my later post which describes some other optimisations I did to get it working even faster.
  1. This is another thing that my tool did (or actually, a second operator I included, for debugging purposes to see which verts it managed to map)

Well then, thank you. I think I actually stumbled across your blog once in the past, maybe even that post, but I think at the time I may have been too new to do much with it. I’ll check it out in a bit, that post is quite lengthy but I’m interested to read what you have there with the knowledge I have now.

  1. There are obviously some dependency-order issues with trying to do that :wink:

Yes, it’s a complex issue, but I think there are some ways of dealing with it, at least to some extent. Or, depending on what you’re referring to specifically, maybe I wasn’t clear… the idea is simply to keep the bone’s rest position in line with the location of the joint. E.g. if you have a proportion-altering morph where the knee edge loop moves upwards an average of .5 Blender units on Z, so does the bone’s rest position. Anyway I’d agree this idea is kind of reaching.

Somehow I’m not surprised I was able to hit the word limit… I’m terrible at being concise.

I made some code, so I figured I’d post it. I have to double post since the above post is at the word limit.

Since this post ended up being ridiculously long I’ll just edit the beginning of it with anything new.

Here is some code for vertex selection based on shape key:


import bpy
from mathutils import Vector
import math

#identify paths
obj = bpy.context.active_object
mesh = bpy.data.objects[obj.name].data.name
verts = bpy.data.meshes[mesh].vertices
keys = bpy.context.scene.objects.active.data.shape_keys.key_blocks
askindex = obj.active_shape_key_index

#declare used variables
deltas = []
count = 0
cutoff = 0.0001

#find the delta of each vertex in the active shape key as a vector
for vert in keys[askindex].data:
    delta = vert.co - keys[0].data[count].co
    #if delta > cutoff set it to selected
    if abs(delta[0]) + abs(delta[1]) + abs(delta[2]) > cutoff:
        verts[count].select = True
    count +=1

Excuse my messiness/horrible coding. As of right now it’s just a preliminary thing I’ve made for my own use but it works. By changing the value of the variable “cutoff,” you can determine the minimum deformation value for which the script selects vertices.

Additionally, by normalizing the values using linear interpolation from 0:1, you could also pretty easily determine the selection by which vertices have the least displacement(just select vertices with a delta only a certain % from 0) instead of cutting off from that. Although there are probably simpler ways of doing that too, I think it will require iterating through all the deltas first to find the maximum at least.

I’ll also note that this script could be modified to both batch delete shape keys under a certain total deform value or to shave off vertices under a certain delta from the basis fairly easily. Maybe I’ll post those later.

I did try reworking my attempt at a transferrer with dictionaries and I believe it’s much faster. I’ll try to post it later if I get some more work done on it. Or if it turns out Aligorith’s code already accomplishes the same purpose as mine I’ll probably just abandon that.

And another thing, if anyone has an answer I’d appreciate it: is there a way to change the index of a vertex without actually creating a new, duplicate mesh? I know how to go about reordering the vertices algorithm-wise but I’m not sure how to actually do it in the API.

EDIT:
I also wrote another piece of code, only indirectly related to shape keys but I’ll post it here.

If you can’t tell by now, I like making different versions of models and having them be adjustable by shape keys. For various reasons this tends to result in models with slightly different sizes but more or less identical meshes. This can be a pain sometimes when for one reason or another I want to copy some kind of data between the meshes, as I have to match their scale and position.

This script should do that automatically, although it has a lot of limitations. Maybe if anyone is interested they could give me advice or edit it themselves for better functionality, or take the idea and make something better. As you’d expect it’s messy and I’m sure I’m doing lots of unnecessary stuff, but it’s what I have for now.

This script is made with the assumption that both objects are mostly the same, or one of their component parts is nearly the same.
Currently, these would be the instructions. In no particular order:
Select an object whose scale and position you want to match to another’s, and at least two vertices on that object.
Select another object, and make a selection on this object that is as analogous as possible to the one you made on the first object(translation:try to select the same vertices).
You only need two vertices per object, but it’s required that both have a distance from each other on the Z axis, and that they’re analogous on the mesh. I.e. a lip vertex and a nose vertex, the same one on both models.

Run the script.


import bpy

obj = bpy.context.active_object
mesh = bpy.data.objects[obj.name].data.name
verts = bpy.data.meshes[mesh].vertices




#define paths for the selected object
sobjs = bpy.context.selected_objects
sobjs.remove(obj)
sobj = sobjs[0]
smesh = bpy.data.objects[sobj.name].data.name
sverts = bpy.data.meshes[smesh].vertices


#apply the transforms for both objects
bpy.context.scene.objects.active = sobj
bpy.ops.object.transform_apply(location = True, rotation = True, scale = True)
bpy.context.scene.objects.active = obj
bpy.ops.object.transform_apply(location = True, rotation = True, scale = True)


#set a flag to check for the first iteration of a loop
first = True
#find the highest and lowest vertices in the selection on the Z-axis for the active object
for vert in verts:
    
    if vert.select == True:
        #declare initial variables for the vertices in the selection with maximum and minimum Z-axis position
        #only runs on the first iteration
        if first == True:
            highest = [vert.co[2],vert.index]
            lowest = [vert.co[2],vert.index]
        #compare the Z coordinates of the current vertex with the maximum and minimum Z coordinates
        #found so far
        if vert.co[2] > highest[0]:
            highest = [vert.co[2],vert.index]
        if vert.co[2] < lowest[0]:
            lowest = [vert.co[2],vert.index]
        
        first = False

#store the highest vertex's coordinates for the active object, probably not needed here
highestvertco = verts[highest[1]].co



sfirst = True
#find the highest and lowest vertices in the selection on the Z-axis for the selected object
for vert in sverts:
    
    if vert.select == True:
        if sfirst == True:
            shighest = [vert.co[2],vert.index]
            slowest = [vert.co[2],vert.index]
            
        if vert.co[2] > shighest[0]:
            shighest = [vert.co[2],vert.index]
        if vert.co[2] < slowest[0]:
            slowest = [vert.co[2],vert.index]
        sfirst = False
        
#store the highest vertex coordinates for the selected object, probably not needed here
shighestvertco = sverts[shighest[1]].co


#calculate a the distance between the selected vertices for both objects
sizez = abs(highest[0] - lowest[0])
ssizez = abs(shighest[0] - slowest[0])

#calculate the ratio of the above distances for both objects
sizeratio = sizez/ssizez

#scale the selected object by the ratio of the distance between the selected vertices
sobj.scale = [sizeratio,sizeratio,sizeratio]

#apply the scale to the selected object so we can calculate the distance between samples of analogous vertices when
#the objects are at equal size
bpy.context.scene.objects.active = sobj
bpy.ops.object.transform_apply(scale = True)
bpy.context.scene.objects.active = obj

#recalcuate the coordinates of the highest vertex in the selection
sverts = bpy.data.meshes[smesh].vertices
highestvertco = verts[highest[1]].co
shighestvertco = sverts[shighest[1]].co

#subtract the position of the active object from the coordinates of the highest vertex in the selected object
transloc = highestvertco - shighestvertco
obj.location =  obj.location - transloc

#apply the transforms to the active object
bpy.ops.object.transform_apply(location = True, rotation = True, scale = True)

The selected object should scale to match the active object, and the active object should move to the selected object.

… What was I doing there…? Oh well. It’ll do for my purposes, for now at least.

Be warned that position, location, and scale are applied for the active and selected objects before running the script(as well as position and scale for the active and selected, respectively, after).

To go along with this code, I also made a script which subtracts the object’s current position on the X axis from the selected vertex’s position on the X-axis. It then applies the object’s position.
This is for those situations where your model is just slightly off center and therefore doesn’t mirror perfectly. Just select a vertex that’s supposed to be at X=0 and run the script. Combined with the above script, it should be easy to set two models to have their X centers at 0 with matching scale and position. I’m sure someone has done this before but it was easy enough so I just wrote it myself.


import bpy

obj = bpy.context.active_object
mesh = bpy.data.objects[obj.name].data.name
verts = bpy.data.meshes[mesh].vertices

for vert in verts:
    if vert.select == True:
        xdisplace = vert.co[0]
        obj.location[0] = obj.location[0] - xdisplace
        
bpy.ops.object.transform_apply(location = True, rotation = True, scale = True)

Looks ok, your first script has an error in the delta calc if you want it to be actual distance (it will still work though, just setting the correct falloff will be a bit weird), it should be something like:


distance = sqrt( x^2 + y^2 + x^2)

(there is probably a builtin function somewhere to get the length of a vector)

You should look into operators, if you implement these scripts as operators you can then create a panel in the UI with buttons to trigger them. You would definitely want to do this if you plan on making them an addon.

Hmm, yeah, I’m not sure why I didn’t just use the distance formula. It’s probably because I started building it as simply a check to see if the delta(s) were greater than 0.

I believe if you import the mathutils module you can simply subtract vectors with that, and objects of vector type have a built-in length method. So I’d imagine rather than using the formula the simplest way would be


deltavec = verts[0].co - verts[thisshapekey].co
delta = deltavec.length

or am I wrong here? I haven’t actually tested to make sure the length method is returning the result I want but I would imagine that’s what it does.

As to operators, I’ve actually already made a couple of addons for my own purposes so I’m familiar with the process, but usually I wait until I’ve used a script a couple of times before I go through the trouble of making it an addon(both to make sure it’s usable as intended/functional in a real situation and to make sure I actually use it).

I wrote some other code which I’ll probably post/edit into this in a bit.

That would be correct, I was posting from work and so didn’t have the API in from of me.

Thats good about the operators, sounds like you are well on your way then.

Alright, I’ll go ahead and correct it when I’m not working on new stuff.

Here’s some more code that’s again only partially related to shape keys.
In the past I’ve found that Blender’s native weight mirroring tools just don’t seem to work the way they need to in order to mirror a model and flip the weights on the mirrored side. At least, I haven’t been able to get them to do what I want. So, I wrote this script to flip vertex weight groups.

What it does, if I’m not mistaken, is create a dictionary of all the vertex groups in a model as keys, storing each vertex’ weights and indices in a list([index,weight]) as a single value(in the keys:value sense), clears the data in the vertex groups on both sides and then uses the dictionary to copy each the data of each vertex group with a name that ends in _L/.L or _R/.R to its mirror group.

Someone else/other people have made plugins like this but either they didn’t quite seem to do what I wanted or they were a little too complicated for me to be able to use their code easily, so I just wrote one myself. That being said I believe I got some of the paths or dictionary creation techniques from some of CoDEmanX’s code.

But it’s been a day or two since I wrote it so my description may be somewhat off as to what the code actually does. For example, I created a dictionary that contains the mirror group of each vertex group(if it exists), but I think I ended up not needing to use it and it can probably be deleted.


import bpy


obj = bpy.context.active_object
mesh = bpy.data.objects[obj.name].data.name
vgroups = bpy.context.active_object.vertex_groups
verts = bpy.data.meshes[mesh].vertices


group_lookup = {g.index: g.name for g in obj.vertex_groups}
group_lookupindex = {g.name: g.index for g in obj.vertex_groups}

#create a dictionary containing group names as keys and lists of vertex indices, weight as values
vertsdict2 = {name: [] for name in group_lookup.values()}
for v in obj.data.vertices:
    for g in v.groups:
        vertsdict2[group_lookup[g.group]].append([v.index,g.weight])




#create a dictionary of vertex group names and the index of their mirror groups
index_of_mirror = {}
for item in vgroups:
    if item.name[-1] == "L":
        mirname = item.name.strip("L") + "R"
        mirindex = vgroups.find(mirname)
        index_of_mirror.update({item.name:mirindex})
    elif item.name[-1] == "R":
        mirname = item.name.strip("R") + "L"
        mirindex = vgroups.find(mirname)
        index_of_mirror.update({item.name:mirindex})


        
for group in vertsdict2:
    if group[-1] == "L":
        gindex = group_lookupindex[group]
        thisGroup = vgroups[gindex]
        for vert in verts:
            thisGroup.remove([vert.index])
        for vert in vertsdict2[group.strip("L") + "R"]:
            thisGroup.add([vert[0]],vert[1],"ADD")

            
    elif group[-1] == "R":
        gindex = group_lookupindex[group]
        thisGroup = vgroups[gindex]
        for vert in verts:
            thisGroup.remove([vert.index])
        for vert in vertsdict2[group.strip("R") + "L"]:
            thisGroup.add([vert[0]],vert[1],"ADD")
        

With this I’ve found I can mirror a model easily without having to apply a Mirror modifier while preserving its weights, so I don’t have to work around the issue of being unable to apply the mirror modifier with shape keys. I’m not really sure if this will work well if there are weights on either side which are intended to bleed a little into the other side, but I think it should work in most situations.

Hopefully I wasn’t the only one unable to get the native weight flipping to work… but at least I’ve familiarized myself with how vertex groups work in the API now.

What I’m thinking of at the moment is a way to improve the mirror shape key tool. Currently, just like mirror selection in Blender, it’s pretty restrictive and, to my knowledge, is only capable of mirroring a vertex’s shape key if there is an exact mirror match. I think both for mirror selection, shape key mirroring, and other things it would be great if there was a way to set a falloff for the program to consider any given vertex a mirror of another. Unfortunately, I’m not really sure if there’s a way to do this besides searching vertices on the other side and running a comparison operation on its coordinates.

EDIT: Here’s a prototype I wrote for that idea.

import bpy
import mathutils


obj = bpy.context.scene.objects.active
mesh = bpy.data.objects[obj.name].data.name
verts = bpy.data.meshes[mesh].vertices


cutoff = .0001
Lside = []
Rside = []
coordsdict = {tuple(vert.co): vert.index for vert in verts}


for vert in verts:
    if vert.co[0] > 0:
        Lside.append(vert.co)
    elif vert.co[0] < 0:
        Rside.append(vert.co)


for vertco in Lside:
    for vertcoR in Rside:
        newcor = vertcoR
        newcor[0] *= -1
        print((newcor - vertco))
        delta = (newcor - vertco).length
        
        if 0 < delta < cutoff or 0 > delta > -cutoff:
            print("mirror")
        elif delta == 0:
            print("mirror")
            

Seems to successfully search for vertices that are within a certain distance from the ideal mirror location, but I can only imagine with a lot of vertices it’d be pretty slow as it is.

Sorry for multiposting again, it just seemed like I might be nearing the word limit on my previous post and I have a couple of other scripts I made. I’m not sure if code inside of code brackets factors into word count, or how this kind of behavior is looked at here.

Anyway, this code is pretty much just a modification of some of the earlier code. Like I mentioned when I posted it, it’s pretty easy to modify it to reset vertex transforms if they’re under a given cutoff value in a shape key, which is what this does. Careful when using it because the only way to get the data back is undo and right now the cutoff is set way higher than would be useful.

NOTE FOR THE TWO SCRIPTS BELOW THIS:
THE VALUE FOR CUTOFF IS NOT WHAT I RECOMMEND. I’ve only set it so large so the effect will probably obvious to anyone running the script. I would recommend using .001 or so. These scripts are not really meant to have creative applications, but to assist with data management and organization.

import bpy
from mathutils import Vector
import math

#identify paths
obj = bpy.context.active_object
mesh = bpy.data.objects[obj.name].data.name
verts = bpy.data.meshes[mesh].vertices
keys = bpy.context.scene.objects.active.data.shape_keys.key_blocks
askindex = obj.active_shape_key_index

#declare used variables
deltas = []
count = 0
cutoff = .25

#set a total count for the delta of the shape key
deltaTotal = 0
#find the delta of each vertex in the active shape key as a vector and do some operation
for vert in keys[askindex].data:
    #store the difference between the coordinates of the vertex in the
    #current shape and the basis shape as a vector
    delta = vert.co - keys[0].data[count].co
    
    #store the total delta for the vertex in the shape key, and add it to the
    #total delta for the shape key
    deltaThisVert = delta.length
    deltaTotal += deltaThisVert
    #if delta > cutoff set it to selected
    if deltaThisVert < cutoff:
        print(deltaThisVert)
        vert.co = keys[0].data[count].co
    count +=1

As to why this is useful, well, sometimes when using options such as “Join as Shapes” you’ll find that the program registers very small transform values for vertices which actually are not intended to move at all, sometimes for the entire model. This can cause problems when you export to other programs because that extra, unneeded data is actually quite considerable depending on the vertex count of the model, to the point where your model’s size can be doubled or tripled from what it should be. This can also cause adverse effects in some 3d programs when loading your model, as the application must also load all shape keys(or, is my guess as to problems I’ve seen).

Anyway, similar to the above, this script checks your shape keys in the same way, but instead if the total delta of all vertices in the shape key is under a certain cutoff value, it deletes the shape key. It repeats this for every shape on the model, from index 1(so, it doesn’t check the basis shape).

import bpy
from mathutils import Vector
import math

#identify paths
obj = bpy.context.active_object
mesh = bpy.data.objects[obj.name].data.name
verts = bpy.data.meshes[mesh].vertices
keys = bpy.context.scene.objects.active.data.shape_keys.key_blocks
askindex = obj.active_shape_key_index

#declare used variables
deltas = []
count = 0
cutoff = 6000

#set a total count for the delta of the shape key

#find the delta of each vertex in the active shape key as a vector and do some operation
count2 = 0
for key in keys:
    if count2 > 0:
        count = 0
        deltaTotal = 0
        for vert in keys[count2].data:
            #store the difference between the coordinates of the vertex in the
            #current shape and the basis shape as a vector
            delta = vert.co - keys[0].data[count].co
            
            #store the total delta for the vertex in the shape key, and add it to the
            #total delta for the shape key
            deltaThisVert = delta.length
            deltaTotal += deltaThisVert
            #if delta > cutoff set it to selected
            count +=1
        #print(deltaTotal)

        if deltaTotal < cutoff:
            print(deltaTotal)
            obj.active_shape_key_index = keys.find(key.name)
            bpy.ops.object.shape_key_remove()
            count2 -= 1
            print(key.name)
    count2 +=1

This one is primarily useful for organization. Currently, to my knowledge there’s no way to eliminate unneeded shapes among needed ones except by hand labor. This is for that kind of situation.

Here’s another. I actually made this one into a function, but I’ve called it at the end of the script.


import bpy

#Moves the currently selected shape key under the basis key
def MoveSelectedUnderBasis():
    obj = bpy.context.scene.objects.active
    askindex = obj.active_shape_key_index
    if askindex != 1:
        range = askindex - 1
        while range > 0:
            bpy.ops.object.shape_key_move(type='UP')
            range -= 1
    elif askindex == 1:
        print()
MSUB = MoveSelectedUnderBasis

MSUB()


This one takes the selected shape key and moves it under the basis shape key. As you might be aware, when you delete a shape key, to my understanding all of the shape keys which use that shape key as their reference/basis shape change their basis shape to the shape key that was under the shape you deleted. In other words, if all morphs have their basis set to the basis key, then when you delete it, the shape that was directly under the basis key is now the new basis. This addon is something I created back before[ I noticed] the “move to top” and “move to bottom” functions were added, in fact I only just remembered as I’m writing this that they were added… oops. Shouldn’t have bothered posting this, but I guess it can’t hurt to have my version here?


import bpy


def BlendActiveShapetoBasis():
    #set up variables
    obj = bpy.context.scene.objects.active
    askname = obj.active_shape_key.name
    mesh = bpy.data.objects[obj.name].data.name
    verts = bpy.data.meshes[mesh].vertices
    
    #ensure we're in object mode
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
    #select the basis shape key
    obj.active_shape_key_index = 0

    selectstates = []
    #save vertex selection state in a list and set all vertices to selected
    for vert in verts:
        selectstates.append(vert.select)
        vert.select = True

    #enter edit mode
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)

    #blend from the active shape key
    bpy.ops.mesh.blend_from_shape(shape=askname, blend=1, add=False)

    #return to object mode
    bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

    #restore vertex selection state
    count = 0    
    for vert in selectstates:
        verts[count].select = vert
        count += 1


BASB = BlendActiveShapetoBasis

BASB()


Hopefully this one is a little more interesting, and fortunately does not overlap with existing tools, to my knowledge. Might be a little buggy, sorry if so. At any rate, the intent is to automatically blend the current shape into the basis key, which is kind of an alternate way of replacing the basis key. Be careful, when you do this you lose your basis key, but you can get it back at any time by setting the shape you blended into the old basis to -1 and then using New Shape from Mix.

It’s honestly a pretty dangerous tool if you don’t know what you’re doing with shape keys, but it’s useful sometimes.

The following is one that simply deletes the basis key, as an alternative to the above and meant to be combined with the script that moves the selected key to the top. As opposed to the above script, the effects on the model are very different(which is why I made them both), but unless someone asks I’ll just assume people are familiar with this.


import bpy

def DeleteBasis():
    obj = bpy.context.scene.objects.active
    obj.active_shape_key_index = 0
    bpy.ops.object.shape_key_remove(all=False)
DELB = DeleteBasis

DELB()


This is really only useful to have if for some reason you don’t want to scroll allllllll the way up to through allllllll of your keys to delete the basis key.
Related, here’s one that makes a copy of the basis key. You pretty much need it in order to be able to return the model to its original state if you use the delete basis method of replacing the basis key. I think simply pressing the “+” button can work too but I think there might be some kind of bug where sometimes the true base shape of the model is not the basis key, so I just made this so I can be absolutely sure(and for practice).


import bpy

def MakeCopyOfBasis():
    obj = bpy.context.scene.objects.active
    #check whether shape key pinning is enabled or not
    pin = obj.show_only_shape_key
    askindex = obj.active_shape_key_index
    
    #select the basis shape key
    obj.active_shape_key_index = 0
    #make sure it's pinned
    obj.show_only_shape_key = True
    #add new shape from mix
    bpy.ops.object.shape_key_add(from_mix=True)
    #restore selection
    obj.active_shape_key_index = askindex
    #restore pinning state
    obj.show_only_shape_key = pin
#shortcut for function
MCOB = MakeCopyOfBasis

MCOB()


I have some more but I’ll leave it at this for now. I have actually made these all into addons, I doubt anyone’s interested but if so I’ll post them in addon form. Please let me know if there are any issues/things I could do better.

A little note regarding conventions, when I code for shape keys I generally name the index of the active shape key “askindex”, short for (a)ctive (s)hape (k)ey index; or in general “ask”=Active sh…etc and basis=0 index key. If I’m not following naming conventions for variables please let me know.

I probably won’t continue posting if there’s no interest, but I did finish a few things. I’ll post this because it illustrates a concept I found to be useful for sorting vertices which maybe others can use.

import bpy
import mathutils


def ShapeKeyDoublesCorrect():
    #define paths
    obj = bpy.context.scene.objects.active
    mesh = bpy.data.objects[obj.name].data.name
    keys = bpy.context.scene.objects.active.data.shape_keys.key_blocks
    verts = keys[0].data
    askindex = obj.active_shape_key_index
    askey = obj.active_shape_key
    cutoff = .0001
    #create a dictionary containing the coordinates of all vertices in the mesh
    #any vertex which has a duplicate vertex is simply appended to the list
    #this will allow us to easily both find and operate on vertices sharing the same coordinates
    coordsdict = {tuple(vert.co): [] for vert in verts}
    count = 0
    for vert in verts:
        coordsdict[tuple(vert.co)].append(count)
        count +=1

    #create a dictionary containing the basis to key delta of vertices in the active shape key
    #vertices with a delta below the cutoff are ignored
    deltadict = {}
    count = 0
    for vert in keys[askindex].data:
        basisshape = keys[0].data
        delta = vert.co - basisshape[count].co
        if delta.length > .01:
            deltadict.update({tuple(verts[count].co):tuple(delta)})
        count+=1

    #search using the delta dictionary; faster this way since only verts involved in the shape key are listed
    for basisco in deltadict:
        for index in coordsdict[basisco]:
            
            deltavec = mathutils.Vector(deltadict[basisco])
            askey.data[index].co = verts[index].co + deltavec


ShapeKeyDoublesCorrect()

Note that your object needs at least a basis key for this to run.

This is much faster than what I had before, because dictionary lookup is much faster than list searching. Before I had the thought to store each vertex as a list instead of an index and append doubles, I wasn’t sure how to sort them well. I still don’t know if this is the best way of doing it, but storing the vertices in a dictionary, then creating a dictionary of deltas and doing operations by cross-referencing the dictionaries is the best method I’ve found so far. I should probably look at what Blender does internally… I hope I can apply this method of creating dictionaries to shape key mirroring for a faster result.


This shows what the script does. The picture is of a mesh with an activated shape key, before and after correction. You can see there are holes in the mesh before the script is run; this is because at some point the vertices were split but only some of the split vertices got the new coordinates, for whatever reason(or the shape key was transferred but if two vertices shared the same coordinates only one got the new coordinates). This kind of thing happens sometimes when you’re not careful about splitting or smoothing vertices. Using remove doubles for this purpose isn’t ideal in my experience, because you can’t determine whether the transformed vertex will be used as the new coordinates for the merged vertex or the basis coordinates. This script also preserves vertex count and index order.

This method could also be used to solve the(extremely annoying) issue of not being able to use smooth operations on a split mesh, albeit with some overhead and somewhat random results as it is now… at least you wouldn’t get a hole.

I also created a script that adjusts a shape key with painted weights. As in, if you had a model with a weight gradient of 0 on Z = 0 weight to 100 on Z = 100 weight, the shape key’s strength would start at 0 and smoothly blend to 100 at Z=100.

EDIT:
Alright, I used this method to create a(hopefully better) shape key transfer script.

import bpy
import mathutils

obj = bpy.context.scene.objects.active
mesh = bpy.data.objects[obj.name].data.name
keys = bpy.context.scene.objects.active.data.shape_keys.key_blocks

verts = keys[0].data
askindex = obj.active_shape_key_index
askey = obj.active_shape_key


for ob2 in bpy.context.selected_objects:
    if ob2.type == 'MESH':
        if ob2.name == obj.name:
            pass
        else:
          selobj = ob2
          selobkeys = ob2.data.shape_keys.key_blocks



cutoff = .01
#coordsdict = {tuple(vert.co): vert.index for vert in verts}
#for vert in verts:

#get a dictionary containing the coordinates of all vertices in the mesh
#any vertex which has a duplicate vertex is simply appended to the list
coordsdict = {tuple(vert.co): [] for vert in verts}
count = 0
for vert in verts:
    coordsdict[tuple(vert.co)].append(count)
    count +=1
#print(coordsdict)





#do the same for the selected object
selobverts = selobkeys[0].data
selobcoordsdict = {}
selobcoordsdict = {tuple(vert2.co): [] for vert2 in selobverts}
count = 0
for vert3 in selobverts:
    selobcoordsdict[tuple(vert3.co)].append(count)
    count +=1







#print(coordsdict)
deltadict = {}
count = 0
for vert in keys[askindex].data:
    basisshape = keys[0].data
    delta = vert.co - basisshape[count].co
    if delta.length > .0001:
        deltadict.update({tuple(verts[count].co):tuple(delta)})
    count+=1



shape_name = askey.name
newkey = selobj.shape_key_add(name=str(shape_name), from_mix=False)


for basisco in deltadict:
    for index in selobcoordsdict[basisco]:
        
        deltavec = mathutils.Vector(deltadict[basisco])
        newkey.data[index].co = selobverts[index].co + deltavec
        



The usage is like the object matcher script I posted above. Select an object, then select one other object. When you press run script, Blender will transfer the active from the active object to the selected object(second selected to first selected). As long as their vertices have the same basis positions, the shape should transfer fine, even if the vertex count/order/etc. is very different. However, I’ve only done very limited testing.

Still need to add a falloff so that verts will be added even if the vertices don’t match exactly.

Also, I think I will use this script to create a shape key mirroring tool. Seems like it will be fairly easy as you can pretty much just use this script and treat the model as actually mirrored by applying negative values to the vertex coordinates, in fact easier than this since you only have to consider half of the model.

Man, I am really wasting my time if there is a shape key transfer tool in development…

Looks good, you may get more feedback in the python forum, or if you made an addon with some of these tools. That said I think shapekeys are planned to be added to the data transfer modifier, so that may end up handling a lot of this.

Re: the dictionary efficiency, dicts are good when your keys tend to be unordered or sparsely distributed. If you have integer keys that are in sequence and you are interested in all or nearly all possible values, then an array indexed by the key may be more efficient (e.g. an array of length 100 could store the paired verts for each vert in mesh with 100 verts, where the index of each vert in the mesh also indexes its pair in the array).

I wouldn’t say I really need too much in the way of feedback, although I’d definitely like it… I just don’t like the feeling of multiposting.

I’ll try to keep that in mind about the dictionary vs list, but it’s a little hard to wrap my mind around. I’m not sure if that addresses the issue of having to search to find the paired verts.

Here’s the script I mentioned that factors vertex groups into a shape key. It creates a new shape key with coordinates that are equal to the difference between the basis and active shape key multiplied by the weight value of the vertices in the vertex group.

To use it, just create a new vertex group, set the weight to 0 and assign all vertices, and then paint as you like. Then select the shape key and run the script. You should find that to an extent you can make smooth adjustments with smooth weight painting.

import bpy


obj = bpy.context.active_object
mesh = bpy.data.objects[obj.name].data.name
vgroups = bpy.context.active_object.vertex_groups
verts = bpy.data.meshes[mesh].vertices
actgroup = bpy.context.active_object.vertex_groups.active
askindex = obj.active_shape_key_index
ask = obj.active_shape_key

keys = bpy.context.scene.objects.active.data.shape_keys.key_blocks

vgroups = bpy.context.active_object.vertex_groups

#declare used variables
deltas = []
cutoff = .25

#set a total count for the delta of the shape key
deltaTotal = 0
#find the delta of each vertex in the active shape key as a vector and do some operation
count = 0
for vert in keys[askindex].data:
    #store the difference between the coordinates of the vertex in the
    #current shape and the basis shape as a vector
    delta = vert.co - keys[0].data[count].co
    deltal = delta.length
    if deltal > 0:
        deltas.append([count,delta])

    count +=1
    
sk = obj.shape_key_add(name = "sample",from_mix=False)
skindex = keys[keys.find("sample")]


for vert in deltas:
    sk.data[vert[0]].co = sk.data[vert[0]].co + (vert[1] * (1 - actgroup.weight(vert[0])))

I’d imagine it’s buggy, but it worked for my tests, at least.

Not super important in this case, but be careful with this, a list is not necessarily an array. There are two common ways of implementing lists, linked lists, and array backed lists, and each has different performance characteristics. In particular, linked lists suffer when trying to access elements in a random order.

Afaik, the standard list implementation in python is array based, so this shouldn’t be a problem.

The array lookup will be vary slightly faster than a dict lookup and use less memory.

If you are interested there is lots of information around the net about standard data structures that looks at the performance characteristics of different kinds of collections etc under different operations, which is pretty useful to understand if you want to get good performance out of programs.

Hmm, alright. I guess I’ll have to research lists a little bit more in order to really understand, though, unless you can give a concrete example(not to brush you off). I was using arrays originally, but the problem was that I couldn’t figure out a way to make it so I didn’t have to compare one list with another when searching for identical vertices. I am interested so I’ll probably check it out at some point, thank you for the information.

I have a question for anyone who knows that’s not related to my above posts/shape keys. I’m trying to work on methods of converting and saving animation data with curves intact(I can export animations with BVH but it simply bakes the animations to every key frame and I lose all interpolation data… not desirable). However, what I’ve found is that in Blender, the way animations are recorded changes when you change the bone’s orientation. For example a bone with a tail at a diagonal relative to the world XYZ coordinates is going to read the same motion/pose differently from another bone with a tail straight up on the Z axis. The pose coordinates will be the same, but unless you change the coordinates of the child object to match the new default bone orientation, a change in transformation will result.

I’ve gotten mostly through it myself(very slowly) but there is one thing I can’t quite get working… a lot of things, to be honest, but in particular what’s troubling me at this moment is this:

How do you compensate for the angle a bone’s rotation changes when its tail is moved? I.e. what kind of calculation do you need to do on the pose to keep the same pose with a different bone orientation?

If I was to give a concrete example…

Let’s say you have a box above a bone.
The bone’s head is at 0,0,0 and the bone’s tail is at 0,0,1, so in Blender default front view the bone is oriented straight upwards.
The box is completely parented and weighted to the bone.
We’ll rotate the bone by 90 degrees on the X axis using the bone’s pose mode.
Now the box should have rotated 90 degrees on the x-axis, following the bone.
Now we go into the bone’s edit mode, select the tail, and then change its coordinates to 0,1,0.
Leaving edit mode, we find the pose has changed.

At first I thought this would be a simple matter(just rotate by the rotation difference between the bone’s original orientation and the one after editing the bone’s tail), and I actually think I was able to solve it at one point so long as I could do it all in one script, but I’m finding that if I want to correct a bone’s orientation after-the-fact, it works alright with a simple one-axis rotation but when you’re dealing with rotation on all 3 axes then it doesn’t seem to be a simple matter of rotating in the opposite direction.

One option I thought of was swizzling, replacing the axes to reflect the new coordinates, but unfortunately many bone structures in Blender consist of bones that are oriented at all sorts of angles. If I wanted to correct them all to be oriented in one direction for whatever reason, I’m not sure what I would do; swizzling for 90 degree axis changes is easy, but I don’t know how to do a half-swizzle, much less a 13% swizzle or something like that.

To be clear, I’m wanting to do this because I’ve found that because Blender registers bone poses relative to their orientation, if you’re exporting motions or poses to a program that registers rotation with each bone normalized to a global XYZ it doesn’t work out.

Or is there a way to access pose information relative to global coordinates? Sorry if I’m not making sense.

EDIT:
For example, I did some more testing and I found that I could correct the pose back to its original coordinates if i did a 90 degree rotation by getting the original pose’s coordinates in XYZ Euler, then flipping the sign of the pose on Y and then swapping the Y and Z coordinates, setting the transform order to XZY. However, I’m still just as clueless as to what to do when using a change that’s not 90 degrees, should I multiply by some % of 90 degrees and the corrected orientation?

I took a break for awhile to pay attention to some other stuff. I overhauled my script for transferring vertices by position, but I’m still working on it.

Mostly what I did was the following:

  1. Find the highest and lowest vertices on any axis
  2. For all vertices, do this operation: (vertex coordinates - lowest vertex coordinate) / (axis dimensions). This produces a value from 0:1, which you can store in a vertex group as a weight for every vertex.
  3. Generate a dictionary which divides all vertices in the model into regions by rounding off the weights to certain precisions. Again, you can assign this to a vertex group if you want.
  4. Repeat for the selected object(since the point of this script is to transfer shape keys from one similar object to another even if there are some(preferably slight) differences in the models)

If both models are very similar, then the regions generated by this algorithm should match up fairly well. Separating the model into separate “zones” should drastically reduce the number of vertices you have to compare to each other. Furthermore, by creating vertex groups from this operation, you can create a pre-composed list of proximate vertices for later use so long as the models’ base shape stays similar.

I just finished testing to make sure it worked, so it’s messy, but here is the comparison part of the code:


cutoff = .0025
#create a list to store found matches based on proximity comparison
matches = []
#create  a dictionary(? should be a list, probably) to store vertices which  didn't find a match and their corresponding space zone
nomatches = {}

#verts = the active object's vertices, selobverts = select object's vertices
#this if statement is actually probably unnecessary...
if len(verts) > len(selobverts):

#ydict  is a dictionary of a weight value and all the active object's vertices  assigned to it based on their position between the "top" and "bottom" of  the model on an axis; in other words, a dictionary of space  zone:vertices

    for item in ydict:

#create a checklist to store vertices which do not have an exact match
        checklist = []
#selydict is ydict for the selected object
        for vert2 in selydict[item]:
#store all verts in the space zone in the checklist
            checklist.append(vert2)

#compare  all of the active object's verts in the space zone against the verts  occupying the same space zone for the selected object
        for vert in ydict[item]:
            for vert2 in selydict[item]:
                dist = (vert.co - vert2.co).length
                if dist < cutoff:
                    matches.append([vert2,vert])
                    try:
#a match was found; remove the vertex from the checklist
                        checklist.remove(vert2)
#a safeguard in case the vertex has already been removed
                    except Exception as e:
                        pass
#now the only vertices in checklist should be the ones that found no match, so add them to a new dictionary
        for nomatchvert in checklist:
            nomatches.update({nomatchvert:item})

#create a new key on the selected object for the shape key to transfer
newkey = selobj.shape_key_add(name="transkey", from_mix=False)

#for  all matching vert pairs, if their offset from basis is > 0, assign  the active object's shape key coordinates to the selected object's new  shape key
for vert in matches:
    delta = askey.data[vert[1].index].co - verts[vert[1].index].co
    if delta.length == 0:
        pass
    else:
        newkey.data[vert[0].index].co = askey.data[vert[1].index].co

#create a new cutoff and find the closest vertex for vertices that didn't find an exact match
cutoff2 = .09
backupmatches = []
#each item is a vertex with its key as a weight value corresponding to a space zone
for item in nomatches:
#create a variable to store the list of vertices in that space zone on the active object
    vertsingroup = ydict[nomatches[item]]
#for all vertices in the active object in that space zone, compare to the selected object's vertex(the one that found no match)
#and append to the list of matches for that vertex
    for vert in vertsingroup:
        dist = (item.co - vert.co).length
        if dist < cutoff2:
            backupmatches.append([item,vert])

#this  is to iterate in case there are multiple vertices which pass the  falloff, but i could probably just do a comparison above and so i doubt  it's needed
for vert in backupmatches:
    delta = askey.data[vert[1].index].co - verts[vert[1].index].co
    if delta.length == 0:
        pass
    else:
        newkey.data[vert[0].index].co = askey.data[vert[1].index].co
   # print(vert[0].index)



I’m sure there’s a lot of terrible stuff I’m doing here but it does seem to work and isn’t terribly slow. Or, even if it is, you can simply store the results of the comparison in a vertex group in both objects, assigning matching vertices to the same weight values, which you can then use to transfer shape keys very quickly at any time without having to do any comparison operations. In other words, the comparisons only need to be done once no matter what the size of the model is as long as their base shape remains fairly close.

In fact, I’m pretty sure you could use this for a lot of other things, and I’m not sure why I’ve never seen it used before, not to sound full of myself. Then again, I suppose there are better ways of doing this anyways, I’m just doing this because I don’t fully know my way around things like sampling UV maps and such.

In any case, the main improvement of this concept over the old one I posted is that it now has a way of transferring to vertices which don’t have exact matches. By storing vertices which don’t have exact matches in a dictionary with their value as a list of vertices in the same space region, finding the closest match among those vertices becomes a quick operation(as opposed to iterating over the entire model for every vertex that didn’t have a match). Simply repeat the comparison for the list of vertices without matches using a higher cutoff and have it copy the closest vertex’s morphed coordinates. You can also smooth this out by dividing the morph offset by some factor of the closest vertex’s distance.

Also note that you can iterate this zone comparison operation on a second/third axis to make it(probably) faster.

This was supposed to be a quick post but it somehow turned into the usual monster… apologies for the useless code, I don’t think I’ll post much more unless I have something major or finished.

Hi,
First I would like to thank you for looking at this matter, blender really could use better shape key transfer tools.
Your script sounds great, I tried to run it (with fairly complicated objects) but I think I failed to copy paste correct parts of your last post to your earlier code snippets so running the script just lead to error > selobverts not defined.