Improving shape key tools

Thanks for your interest. Yeah, that code isn’t intended to be runnable by anyone so much as I posted it to show the conceptual framework of the script, sorry about that.

I’m still pretty new to programming and there’s a lot of different stuff I’m trying to put together which kind of overwhelms me at times, especially regarding animation.

I’m sorry though, I still haven’t finished that script completely. I have it in working order, along with a script that’s a little better at mirroring shape keys(arguably, I’d say), but I still want to improve the details before I post a functional version here.

In the meantime, I do have something else I just completed, though. To liberally sum it up, it applies an object’s first modifier whether or not the object has shape keys, and works with subd, solidify, and some others.


import bpy
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
askindex = obj.active_shape_key_index
askey = obj.active_shape_key
data = bpy.data
objs = bpy.data.objects


def getobjindex(object):
    count = 0
    for ob in objs:
        if ob == object:
            objindex = count
            break
        count+=1
    return objindex


def create_modified_duplicate():
    modified = obj.copy()
    modified.name = "modified"
    modmesh = modified.data.name
    modmeshdup = bpy.data.meshes[modmesh].copy()
    modified.data = modmeshdup
    modkeys = modified.data.shape_keys.key_blocks
    modmods = modified.modifiers
    bpy.context.scene.objects.link(modified)
    bpy.context.scene.objects.active = modified
    bpy.context.scene.update()
    modified.select = True
    obj.select = False
    bpy.context.scene.objects.active = modified
    
    bpy.ops.object.shape_key_remove(all=True)
    bpy.ops.object.modifier_apply(apply_as='DATA',modifier = modmods[0].name)
    
    return modified



modified = create_modified_duplicate()
bpy.context.scene.objects.active = obj
obj.select = True

objindex = getobjindex(obj)
count = 0

sos = obj.show_only_shape_key
obj.show_only_shape_key = True
for shape in keys:
    if count > 0:
        obj.active_shape_key_index = count
        bpy.ops.object.object_duplicate_flatten_modifiers()
        shapeobj = objs[obj.name + "_clean"]
        shapeobj.name = obj.active_shape_key.name
        shapeobj.select = True
        bpy.context.scene.objects.active = modified
        modified.select = True
        obj.select = False
        bpy.ops.object.join_shapes()
        bpy.context.scene.objects.active = obj
        bpy.context.scene.objects.unlink(shapeobj)
    count +=1
obj.show_only_shape_key = sos
    


Quick notes:

  1. You’ll need to enable the Corrective Shape Keys addon to use this.
  2. DO NOT use this on a complicated object with a lot of shape keys. Depending on the strength of your computer it may be OK, but if you have 100 shapes on a million-vertex object or something like that, it’s probably going to create a heavy memory usage on your computer.
  3. I’ve tested it with subdivision surface and solidify, at least. It should work with others as well.
  4. Examples of modifiers it’s not likely to work with are Edge Split(it will work if you’re only using sharp marks, though), decimate, bevel, mirror with the merge option on–basically, anything that will result in varying vertex counts between different shapes for whatever reason. But, there shouldn’t be any harm in trying anyway.
  5. Make sure you only have one modifier on the object. Sorry about that, I should be able to fix it easily some other time.

If you run this script on an object with a modifier that DOES change vertex count but results in the same vertex count and order no matter what the shape is, then this script should generate a new object(called “modified”) that has modifiers applied and possesses all of the original’s shape keys. Vertex groups and all other data should remain intact, since the new object is just a duplicate of the original with the modifier applied.

It may not work correctly on all scenes or objects, but in general this is the flow of what it does:

  1. Duplicate the active object
  2. Duplicate the active object’s mesh and assign the duplicate object the new mesh(so they don’t share shape key collections)
  3. Clear the duplicate object’s shape keys
  4. Apply the first modifier on the object
  5. Iterate over the active object’s shape keys:
    4a)use the bpy.ops.object.object_duplicate_flatten_modifiers() operator on each(creates a duplicate object from the shape key with all modifiers applied), give it the name of the active shape key
    4b)select the duplicate object, make it the active object, use Join As Shapes
    4c)remove the object created in 4a
    4d)set the active object to the original object, repeat

One last note. There is a bug I’ve encountered quite often in Blender where the actual shape of the model is registered as something that is not equal to any of the shapes on the list. If you use the “delete all shapes” option, sometimes your model will become splattered(for lack of a better word), which is the “actual state” of the model(I guess). Since this script uses the “delete all shapes” operator, I’m not sure how this script will function when that happens, but you should be able to fix it by making a copy of the basis shape and then making that copy the new basis shape by deleting the current basis shape while the copy is directly under it(sorry if that’s confusing).

If anyone has problems please let me know.

EDIT: Made a slight revision to the code, if it didn’t work before try again
EDIT: And another…

EDIT3:
Ahh, while I’m at it, here’s this:



import math
import bpy
from numpy import array
from mathutils import Vector
import os
os.chdir("C:/Users/Me/Documents/pythonfiles")
import mathutils
p = print
import bmesh


obj = bpy.context.scene.objects.active
mesh = bpy.data.objects[obj.name].data.name
verts = bpy.data.meshes[mesh].vertices
keys = obj.data.shape_keys.key_blocks
askeyv = obj.active_shape_key.value
sosk = obj.show_only_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
          

count = 0
for key in selobkeys:
    
    if count == 0:
        pass
    else:
        shape = selobkeys[count]
        keyname = shape.name

        newkey =  obj.shape_key_add(name=keyname, from_mix=False)
        countverts = 0
        for v in verts:
            keys[-1].data[countverts].co = shape.data[countverts].co
            countverts +=1


    count +=1



This just transfers all of the shape keys of the active object to the first selected object. It should run even if the vertex count isn’t the same, but it won’t work correctly if the objects’ vertex order isn’t the same from 0. This is actually a little important, because it means if you, say, add a hat to a character, you could still run the code on it and it would work since the hat would simply be appended to the end of the vertex list and the vertex order is otherwise the same.

This goes for both of the scripts I posted, but sorry if there are some useless bits of code in there. If anyone’s looking at it to study(I’d be honored but I don’t advise it) be aware that the code isn’t polished and some things are just left over from testing and such(or I don’t remember if they were necessary and too lazy atm to review the code and check).

As always if anyone has comments/criticism/suggestions/etc. I’d be more than happy to hear it.

I’ve found that the best way of creating a spatial “net” to catch vertices is rounding their coordinates or simply dropping precision. Is there a faster/more intelligent way to do this?

EX:
Vert1’s X coord = 2.34335355
Vert2’s X coord = 2.3433648392
Vert3’s X coord = 2.34434953

If you round these numbers to the 4th decimal, Vert1 and Vert2 will be counted as matches. Using Vert1’s coordinates as a basis, in a metaphorical sense you could call this a net that encompasses any space around 2.3434(Vert1’s X coord rounded) between 2.3434499~ and 2.34335, or at least that’s how I’m using it.

Does anyone know a more efficient method? Am I slowing my computations down by rounding and I should use some other process(like int casting to drop precision)? I’m trying to look at the remove doubles operator and others to see how they do it but I’m still just learning C.

EDIT: I had another question as well. Is there an accepted method for saving lists of data and such in a menu or something? I believe a lot of otherwise computationally intensive processes would be made much easier by gathering data before running an addon, and I know I can do this personally simply by saving a text file to some location, but if I was to make some of the things I’ve made into an addon for general use I don’t think that would work out very well.

Or are custom lists and such of data gathered by an addon/script more or less something you need to program in C? I looked at an addon I know that does this and I found that the source code for the panel appears to be in C.

Figured I’d just make a new post since this is a release although the last two posts are mine…

Attached is a beta of sorts of the shape key transfer addon. I guess it’ll just be rendered obsolete whenever the devs release shape key transfer for the data transfer addon(or rather I’ve already seen a video where someone showed something which seems to work much better than mine but I’ve never been able to actually find a Blender build that has it)… At least this will be usable in older versions of Blender, I guess.

I read over it again and some of the comments in the code are leftover from my dev process, sorry.

Here’s a pastebin of the source code, with a couple corrections.
http://pastebin.com/Xu59sSr2


^Shoddy use instructions

I’ve barely tested it myself, but it’s a full addon. I’m probably breaking a lot of Blender conventions with my implementation, for which I apologize, but it is beta level at best anyway. To install it, you should be able to use the “install addon from file” option inside of User Preferences on the .py file included in the zip attached to this post.

I’m not sure of the limitations on this. I’m sure there are many problems with it. If anyone has corrections or criticism to my code or methodology they’re very welcome.

This addon is executed from a new tab, named “Shape key nonsense,”(maybe I’ll change it when I feel I’ve completed it well). My current plan is to use this tab for shape key tools I make personally.

A couple notes:
This is a beta at best. Save before you use it. I can’t recommend using it on very high poly models.
There are two values you can adjust for this addon after execution, precision and scale; note, though, that the addon must completely re-execute when you adjust them. I recommend manually typing in the values instead of dragging the bar.
This addon both adjusts precision and scales values to compare vertices. I found that while it’s easy to adjust precision for large errors, it doesn’t provide enough control if only one or two vertices were missed.
I recommend between 3-6 for precision. Dropping precision too low seems to result in very long operation times, so be careful.
If you’re only missing a couple vertices in the transfer, then try lowering the scale by half a magnitude or so until they transfer successfully.
You can try to do multiple shape key transfers at different settings and blend the parts that didn’t work in one trial with parts that worked well in another manually using the Blend From Shape feature in edit mode.
I had thought that low values of precision like 0,-2, etc. would work horribly, but it seems like sometimes they give the best results; the tradeoff being that values like that are horribly slow(“time to go get some breakfast” level).
This addon is intended for very similar models. It can work for models with some differences, but the speed of the addon decreases greatly as fewer and fewer exact position matches are found… I think.
I may write a lot but don’t be deceived: I really have no idea what I’m doing.

Other stuff:
In hindsight I really screwed up with this, actually. I should have made it so the end result of execution has the shape key activated on the target object. As it stands, you can’t really adjust the values dynamically due to this oversight. I’ll change the functionality later.

As to what the precision and scale values do, this addon basically runs by finding all exact position matches of vertices between the selected and active object first(storing all vertices with no exact match), then runs a second pass where it rounds and scales all compared coordinates in an attempt to find nearby matches. Then, using the matches found between the two passes, it copies the active shape key coordinates of the matches found in the active object to a new shape key in the selected object for all vertex matches found.

I think the reason that using low values for precision bloats the run time so much is that I’ve included a function which, if multiple matches are found for a vertex, the vertex is compared to every matching vertex to see which is the closest. So, if a large number of matches are found for a large number of vertices, this results in an exponential(I think?) increase in the number of comparisons, which is usually not actually necessary(since all the program really needs to do is find one close match). I programmed this in because as I said this addon is intended for closely matching meshes, and therefore it’s assumed there won’t be very many vertices without matches. I may make an option to disable it or use a different approach in the future, but in general, as long as the “net” you’ve created with the precision and scale values(as I described in the above post) isn’t so big that many erroneous matches are found, I don’t think it should cause much of an issue.

If no match is found for any vertex during the second pass, it’s simply ignored; this can create very ugly/bad spikes which you’ll have to clean up yourself, but the intent of this addon is more to transfer shapes between objects which only have slight differences; I don’t think it would work well for something like low>high poly transfer. That being said, I may alter the code at some point to provide an option to try copying shape key coordinates from connected vertices(or their average) for vertices which have no match.

I guess I’ll use this last line to apologize for all the terrible things I’m sure I’ve done with this code. Once again, feel free to criticize whatever/however you please, and please report any bugs/problems.

Attachments

shapenonsense2.zip (99.4 KB)

1 Like

Hi,
I tried your script and I’m amazed. It’s working really nicely with my tests. The result is not perfect but it’s a really good base where to manually fix the deformations than just to make everything manually again.
Something like this should be included in blender. I hope you continue improving your little script because it’s really nice and actually easy to use :slight_smile:

Thanks!

Yeah, it’s far from perfect but it should work for at least the situations it’s intended for. I remember when I was learning 3d I would make small mistakes all the time that would lead to me losing all of my shape keys because of some error I didn’t have the knowledge or experience to foresee, like how removing doubles, knifing, subdividing, face ripping, etc. can all cause a mesh’s vertex order to change and all of a model’s shape keys to be destroyed, which can be irreversible if you don’t notice early enough. With this script as long as you have a backup of the model your shape keys are mostly safe, so I think it would provide a huge benefit particularly to less experienced people who like to work with/make shape keys.

Anyway, hope it’s of some help.

I made a small update to the script, correcting the problems I mentioned above. Now, when the script ends its execution, it sets the selected object’s active shape key to the last shape key on the list(which should be the new one) and pins it, and optionally hides the active(shape key source) object. This should make it easier to use the script; before, in order to view the results of execution, you had to select the object and activate its shape key, which made it so you couldn’t adjust the addon options dynamically after running it.

There’s a new option, “hideobj,” which hides the active object so it’s easier to see the results of the addon execution. You can prevent this by setting “hideobj” to 0(or simply unhide the object, but I believe that will end the script execution context).

To change the default behavior of hiding the source object, in the .py file simply change the
hideobj =bpy.props.IntProperty(default =1)
line to

hideobj =bpy.props.IntProperty(default =0)

Long story short now you can run the addon and then adjust the addon options while looking at the results, and it hides the active object.

I’ll probably post another tool or two later today. I really have a lot of things to share, but they’re all packed into one huge addon folder I’ve made for my own use so I have to clean them up and take them out before I feel comfortable posting them.

Attachments

skn1.001.zip (1.91 KB)

I just went ahead and packed up another one really quickly.

This is a little more intuitive. It’s just an alternative to Blender’s default mirror select.

This time, it appears in a list under the Tools tab(t in the 3d view, top tab(by default)). It should appear at the very bottom. The usage is pretty intuitive. Just select a vertex on the model, activate the addon, and a mirror vertex should be selected. If none are selected, or you want to increase the size of the selection, you can drag the “falloff” variable on the bottom left to adjust the range of the mirror.

I’ve found that on higher poly models it can run a little slowly, but just as before consider this a beta. I’ll try to improve it later.

Other features I plan to add to this specific tool are the ability to mirror based on the model’s default shape(basis key, no modifiers, etc) and to mirror based on the mesh’s X center(currently, if you try to mirror a model whose object position is not at its X center the mirror won’t work), as well as the ability to mirror either edge or face selection.

Installation should be as easy as the first addon; just select the zip or py file with Install from File inside User Preferences and activate the check box.

WARNING: SAVE BEFORE USE AND BE SURE THERE’S NO NEED TO KEEP YOUR UNDOS.
I just tested on a UV sphere subdivided 3 or so times, and it crashed Blender. I have no idea why, except it must just use too much memory with the calculations I’m using if the mesh density is too high. It was awhile ago that I made this, so it’s probably really inefficient. So, use with caution, especially on very high poly models. Still, it should allow you to mirror select automatically in many situations where it would otherwise be impossible, so I’ll leave it up.

If anybody can help me with a way to safeguard against a crash at the very least, I’d appreciate it.

Attachments

nmt1.000.zip (1.11 KB)

I´m not that sure if my question is at correct place in this thread, but at least
it concerns the functionality of shape keys.
Recently i ran into a somewhat missing feature : Transferring shape keys work with
mesh objects, but obviously not with curves. It´s offered in the popup menu,
but when you try you get an error message. Is there any reason for ?
Am i the only one who is missing this feature ?
I´m still with vs.2.71 - is it fixed in a newer version ?
Is there some workaround to have shape key transfer on curves ?
Any advice is welcome - by the way, i am a simple user, and not into coding yet.

I’m the creator of this thread and I don’t mind your question, at least, so don’t worry about it.
I’m not sure what the official status on this is but it should be very simple to implement.
As it stands, I tried in 2.74 and I encountered the same error. I’ve never really used shape keys with curves before, but I can see it’s a problem.

The only psuedo-workarounds I can think of are either limited in scope or workflow-based. Depending on the nature of the problem, you may be able to deal with it by converting the shapes you want to transfer to meshes and then using join shapes on the meshes(but then you’ve lost your curves), or you could possibly try to use a workflow that replaces shape keys with something else, like armature or vertex parenting with poses/keyframing. Or, I don’t know if this is possible, but you could parent the vertices of the curve to that of a matching mesh, and then control the curve with the mesh’s shape keys.

Other than that, at present I can’t think of a real workaround, but writing code to do the job should be pretty simple for anyone who knows their way around Blender’s API. I could write a simple one for you, but on second thought there are a lot of types of curves and whatnot and while it is a simple task to write, at this moment I’d rather not spend time writing one that accomodates all curve types. If you’re ok with just a bezier curve shape key transferrer, I’ll write a simple one quickly for you.

EDIT:
Or, here, I went ahead and wrote one for the hell of it:


import bpy

def get_sel_obj():
    obj = bpy.context.active_object
    for ob2 in bpy.context.selected_objects:
        if ob2.type == 'CURVE':
            if ob2.name == obj.name:
                pass
            else:
              selectedobj = ob2
    return selectedobj

obj = bpy.context.active_object
keys = obj.data.shape_keys.key_blocks
askey = obj.active_shape_key
askindex = obj.active_shape_key_index


selobj = get_sel_obj()

selobjkeys = selobj.data.shape_keys.key_blocks


nsk = selobj.shape_key_add(name = askey.name,from_mix = True)

count =0
for vert in nsk.data:
    vert.co = askey.data[count].co
    vert.handle_right = askey.data[count].handle_right
    vert.handle_left = askey.data[count].handle_left
    count +=1


Designed for a bezier curve generated from shift a>add curve>bezier, probably won’t work with things like NURBS curves/surfaces/etc. I might have missed a property or two that can be transferred between keys, at present it transfers position as well as the left+right handles of each vertex.

It transfers shape keys by index, so although it’s not required, for proper functionality the vertices involved in the shape key should have the same indices in both curves. In other words, the curves should share the same base shape. To use it,

  1. make sure both objects have at least a basis key(you’ll get an error if there are no keys)
  2. select the curve to receive the shape key
  3. select a curve with the shape key you want to transfer
  4. run the script.

If you need something for a different type of curve or it otherwise doesn’t work, let me know.

This thread is such a mess of text that I feel like I should just summarize what I’ve done so far.
Still seems like there’s just not much interest but if someone wants one of these feel free to ask and I’ll try to pack it into an addon or something.

This is a summary of functions/tools I’ve made for Blender. I believe I’ve accomplished the goals I set in the OP, more or less. You can assume everything listed here is basically automated.

Mesh:

  1. Snap all vertices in a selection to their closest vertex in a disjointed selection; works like Loop Tools’ Bridge, but snaps the vertices instead and possibly more flexible(on the other hand, probably more error-prone)
  2. Attempt to match the scale and position of two similar meshes
  3. Alternate mirror selection tools; falloff for mirror recognition is adjustable so even if no exact mirror exists, the mirror will function. Also capable of mirroring based on the mesh’s local X center

Armatures:

  1. Option to reset all of the armature’s bone tails to what I call Blender’s identity vector(head vector+[0,1,0], 0 roll), or only the selection. This causes the pose of bones as seen in the Properties panel of the 3d view(and easily accessible through scripting) to be WYSIWYG relative to Blender’s world.
  2. Weight transfer based on selected vertices
  3. A tool to flip vertex weights
  4. Normalize a Euler rotation in a button press; I don’t know if there’s a way to do this currently without switching back and forth between Quaternion and Euler manually
  5. Similarly, an alternative keyframe register which first normalizes the Euler rotation
  6. Set the inheriting options for all bones
  7. Enable/disable connected for all bones
  8. Disable transform locks for all bones
  9. Mute all IKs or disable all bone constraints
  10. Set all bones to hide or show
  11. Create a vertex group on the active object containing weights which correspond to the vertex index of the closest vertex(or a close vertex) in the selected object for all vertices in the active object, with an adjustable falloff; mainly useful for scripting
  12. Create vertex groups on the active object with weights based on rules like X position, Z position, normal, etc. Also pretty much only useful for scripting, I think

Shape keys
A button to…

  1. create a negative of a shape key(useful for exporting to programs which don’t support negative shapes)
  2. set all or the active shape keys’ maximum and minimum values to a certain value
  3. batch transfer all shape keys from one model to another
  4. import all objs in a folder path to the active object as new shapes
  5. transfer shape key from selected to active object based on vertex position, and a batch transfer option
  6. mirror shape keys; can mirror from left to right, right to left, or flip the shape; adjustable falloff for mirror recognition
  7. delete all shapes above or below the active shape key
  8. replace the basis shape with the active shape, either by deleting or blending
  9. duplicate the object with its first modifier applied and all shape keys; some modifiers are not compatible
  10. import/export shape keys as CSV files
  11. select all vertices involved in a shape key
  12. fix gaps in a shape key caused by smoothing
  13. apply subdivide smooth modifier to a selected portion of the mesh, preserving the shape keys, and also automatically dissolves vertices at the border on the new subdivided mesh so that the edges match; limited usability because it can be a very intensive process depending on the number of shape keys,subds, and vert count
  14. transfer shapes between vert selections or materials
  15. export/import shapes by material
  16. set a shape key to -1
  17. set the relative key of all shape keys to the active shape key
  18. shift default pose of armature+model: copy the object’s armature modifier, apply it as a shape, make it the basis shape, and then apply the armature’s rest position to its current pose
  19. Clean a shape key up by resetting the transforms of any vertices with transforms below a given cutoff; useful when using things like SoftImage’s GATOR or Zbrush to make shape keys because sometimes all vertices are registered with very low transform values which will bloat data significantly upon exporting to other programs

(I also wanted to mention that one of the biggest things I said I wanted to make in the OP WAS already a feature. You can use weight paint mode like a sculpting mode for shape keys by assigning vertex groups to the Blend function under the shape key list. I’m an idiot for not seeing/realizing this use for it until today, much less creating an addon that doesn’t even work as well)

Materials:

  1. Set all material transparencies to on or off
  2. Set all material backface culling to on or off

General

  1. clear unused data from the scene; this includes inactive objects, meshes, textures, etc. Can be done separately. I think other addons exist for this too
  2. forcibly clear all textures and/or materials from an object; can cause related data to be unlinked or deleted. Sometimes useful when exporters are stubborn about texture paths and whatnot

That’s what I’ve got so far. I’ll probably just edit this information into the OP and update as I make things. If anyone’s interested in any of the above features, or any feature that gets added to the OP after this, go ahead and post saying so; I consider a lot of the features I create to be things which only satisfy my personal needs, so I’m just going to do addon creation by request and provide a catalog.

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.As shape keys are non-destructive, you can try out various ideas on changing or improving a model, and if you find you don’t like them.Where to Find Shapes; Where to find Drawing Tools; How to Select Shapes; How to Copy Option 1) Hold down the CTRL key as you left-mouse-click multiple objects.An application removal tool like PC Decrapifier can help in those scenarios.After selecting everything you’d like to delete, click the OK button and the quickest and easiest ways to improve the overall the performance.


Property Management

…just found your reply, somewhat three years later…thank you for the python script, i will try it. This function could still be useful. BTW, recently i use Sverchok for curves animation.