[Addon] ShapeKey Helpers

Hello everyone!

I noticed that the old thread about the ‘ShapeKey Helpers’ addon I wrote a few years ago seems to have been lost when the forum switched over to Discourse.

Since there still seems to be some interest in the addon, I’m just going to open a new thread here so people at least have a place to ask questions and leave feedback.


What is ‘ShapeKey Helpers’? What can I use it for?


It’s a little addon that speeds up certain repetitive tasks when working with shapekeys and when exporting objects with shapekeys (so they can be used in other software, such as Unity, Unreal […]).

The addon is composed of 3 operators, which are explained and presented very nicely in this blogpost (thanks @Looch!).

I’d also like to thank @mem for integrating the operators into the UI and making the addon a lot more user-friendly as a result.


Is this addon still being worked on?


Unfortunately not. I don’t really have enough spare time to keep improving the addon. On top of that, I’m not a particularly great python coder either.

I’m happy to answer questions and help out though! Just feel free to ask and I’ll try my best to help!

If anybody feels like continuing the work on this addon, please feel free to do so!


Where can I download this addon?


All releases of this addon can be found and downloaded here: https://github.com/JanOtt/ShapeKey-Helpers/releases

If you’re using Blender 2.79 (or older), please use version 1.01 of this addon. If you’re on Blender 2.8 (or newer), get version 1.1 instead.


I hope you’ll find this useful!

12 Likes

Thanks for re-opening this thread… I think this is a great tool and it deserves to live on.

Someone should submit this for implementation into 2.80 if it solves the “Cant apply modifiers with shape keys” stuff.

Just a thought

I wanted to use this (very helpful) plugin that @J_Ott made, so I updated it to 2.8. I’ve tested it as much as I can and had good results, but caveat emptor. (caveat blendor? :laughing:)

bl_info = {
    "name": "ShapeKey Helpers",
    "author": "Ott, Jan",
    "version": (1, 1, 0),
    "blender": (2, 80, 0),
    "description": "Adds three operators: 'Split Shapekeys', 'Apply Modifiers and Keep Shapekeys' and 'Apply Selected Shapekey as Basis'",
    "warning": "",
    "wiki_url": "https://blenderartists.org/t/addon-shapekey-helpers/1131849",
    "category": "'Mesh",
}

import bpy
from inspect import currentframe, getframeinfo


#__________________________________________________________________________
#__________________________________________________________________________


def SetActiveShapeKey (name):
    bpy.context.object.active_shape_key_index = bpy.context.object.data.shape_keys.key_blocks.keys().index(name)
    
#__________________________________________________________________________
#__________________________________________________________________________

O = bpy.ops

class ShapeKeySplitter(bpy.types.Operator):
    """Creates a new object with the shapekeys split based on two vertex groups, named 'left' and 'right', that you must create manually"""
    bl_idname = "object.shape_key_splitter"
    bl_label = "Split Shapekeys"

    def execute(self, context):
        
        O.object.select_all(action='DESELECT')
        bpy.context.active_object.select_set(True)
        #____________________________
        #Generate copy of object
        #____________________________
        originalName = bpy.context.object.name
        O.object.duplicate_move()
        bpy.context.object.name = originalName + "_SplitShapeKeys"


        listOfKeys = []

        index = 0

        #__________________________________________________

        for s_key in bpy.context.object.data.shape_keys.key_blocks:
            
            if(index == 0):
                index = index + 1
                continue 
            
            if s_key.name.endswith('.L') or s_key.name.endswith('.R') or s_key.name.endswith('.B'):
                continue
            
            
            listOfKeys.append(s_key.name)

        #__________________________________________________

        for name in listOfKeys:
            
            SetActiveShapeKey(name)
            
            savedName = name
            savedShapeKey = bpy.context.object.active_shape_key
            
            
            #Create left version
            
            O.object.shape_key_clear()
            
            SetActiveShapeKey(savedName)
            savedShapeKey.vertex_group = 'left'
            savedShapeKey.value = 1.0
            
            O.object.shape_key_add(from_mix=True)
            bpy.context.object.active_shape_key.name = savedName + ".L"

            
            #Create right version
            
            O.object.shape_key_clear()
            
            SetActiveShapeKey(savedName)
            savedShapeKey.vertex_group = 'right'
            savedShapeKey.value = 1.0
            
            O.object.shape_key_add(from_mix=True)
            bpy.context.object.active_shape_key.name = savedName + ".R"
            
            
        for name in listOfKeys:
            
            #Set index to target shapekey
            SetActiveShapeKey(name)
            #Remove
            O.object.shape_key_remove(all=False)
                
                
        return {'FINISHED'}
    

class ShapeKeyPreserver(bpy.types.Operator):
    """Creates a new object with all modifiers applied and all shape keys preserved"""
    """NOTE: Blender can only combine objects with a matching number of vertices. """ 
    """As a result, you need to make sure that your shape keys don't change the number of vertices of the mesh. """
    """Modifiers like 'Subdivision Surface' can always be applied without any problems, other modifiers like 'Bevel' or 'Edgesplit' may not."""

    bl_idname = "object.shape_key_preserver"
    bl_label = "Apply Modifiers and Keep Shapekeys"
    
    def execute(self, context):
    
        oldName = bpy.context.active_object.name
        
        #Change context to 'VIEW_3D' and store old context
        oldContext = bpy.context.area.type
        bpy.context.area.type = 'VIEW_3D'

        #selection setup
        originalObject = bpy.context.active_object

        originalObject.select_set(True)

        listOfShapeInstances = []
        listOfShapeKeyValues = []

        #_______________________________________________________________

        #Deactivate any armature modifiers
        for mod in originalObject.modifiers:
            if mod.type == 'ARMATURE':
                originalObject.modifiers[mod.name].show_viewport = False

        index = 0
        for shapekey in originalObject.data.shape_keys.key_blocks:
            if(index == 0):
                index = index + 1
                continue
            listOfShapeKeyValues.append(shapekey.value)

        index = 0
        for shapekey in originalObject.data.shape_keys.key_blocks:
            
            if(index == 0):
                index = index + 1
                continue
            
            bpy.ops.object.select_all(action='DESELECT')
            originalObject.select_set(True)

            bpy.context.view_layer.objects.active = originalObject
            
            bpy.ops.object.shape_key_clear()
            
            shapekey.value = 1.0
            
            #save name
            #____________________________
            shapekeyname = shapekey.name
            
            #create new object from shapekey and add it to list
            #____________________________
            bpy.ops.object.duplicate(linked=False, mode='TRANSLATION')
            bpy.ops.object.convert(target='MESH')
            listOfShapeInstances.append(bpy.context.active_object)
            
            #rename new object
            #____________________________
            bpy.context.object.name = shapekeyname
            
            bpy.ops.object.select_all(action='DESELECT')
            originalObject.select_set(True)

            bpy.context.view_layer.objects.active = originalObject

        #_____________________________________________________________
        #Prepare final empty container model for all those shape keys:
        #_____________________________________________________________
        
        bpy.context.view_layer.objects.active = originalObject
        bpy.ops.object.shape_key_clear()

        bpy.ops.object.duplicate(linked=False, mode='TRANSLATION')
        newObject = bpy.context.active_object

        bpy.ops.object.shape_key_clear()
        bpy.ops.object.shape_key_remove(all=True)

        newObject.name = oldName + "_Applied"

        for mod in newObject.modifiers:

            # Not actually sure why this is necessary, but blender crashes without it. :| - Stel
            bpy.ops.object.mode_set(mode = 'EDIT')            
            bpy.ops.object.mode_set(mode = 'OBJECT')            
            bpy.ops.object.modifier_apply(apply_as='DATA', modifier=mod.name)

        errorDuringShapeJoining = False
            
        for object in listOfShapeInstances:
            
            bpy.ops.object.select_all(action='DESELECT')
            newObject.select_set(True)
            object.select_set(True)

            bpy.context.view_layer.objects.active = newObject
      
            
            print("Trying to join shapes.")
            
            result = bpy.ops.object.join_shapes()
            
            if(result != {'FINISHED'}):
                print ("Could not add " + object.name + " as shape key.")
                errorDuringShapeJoining = True

        if(errorDuringShapeJoining == False):
            print("Success!")
                
        if(errorDuringShapeJoining == False):
            #Reset old shape key values on new object
            index = 0
            for shapekey in newObject.data.shape_keys.key_blocks:
                if(index == 0):
                    index = index + 1
                    continue
                shapekey.value = listOfShapeKeyValues[index-1]
                index = index + 1

        #Reset old shape key values on original object
        index = 0
        for shapekey in originalObject.data.shape_keys.key_blocks:
            if(index == 0):
                index = index + 1
                continue
            shapekey.value = listOfShapeKeyValues[index-1]
            index = index + 1
            
            
        #Select and delete all temporal shapekey objects       
        bpy.ops.object.select_all(action='DESELECT')

        for object in listOfShapeInstances:
            object.select_set(True)
            
        bpy.ops.object.delete(use_global=False)
        
        
        #Reactivate armature modifiers on old and new object
    
        for mod in originalObject.modifiers:
            if mod.type == 'ARMATURE':
                originalObject.modifiers[mod.name].show_viewport = True

        for mod in newObject.modifiers:
            if mod.type == 'ARMATURE':
                newObject.modifiers[mod.name].show_viewport = True
                
        bpy.context.area.type = oldContext
        
        return {'FINISHED'}
    
    
    
class ShapeKeyApplier(bpy.types.Operator):
    """Replace the 'Basis' shape key with the currently selected shape key"""
    bl_idname = "object.shape_key_applier"
    bl_label = "Apply Selected Shapekey as Basis"
    
    def execute(self, context):
        
        O.object.select_all(action='DESELECT')
        bpy.context.object.select_set(True)

        #____________________________
        #Generate copy of object
        #____________________________
        originalName = bpy.context.object.name
        O.object.duplicate_move()
        bpy.context.object.name = originalName + "_Applied_Shape_Key"

        shapeKeyToBeApplied_name = bpy.context.object.active_shape_key.name

        listOfKeys = []

        #__________________________________________________
        #Store all shape keys in a list 
        #__________________________________________________

        for s_key in bpy.context.object.data.shape_keys.key_blocks:
            
            if s_key.name == shapeKeyToBeApplied_name:
                continue
            
            listOfKeys.append(s_key.name)

        #__________________________________________________

        for name in listOfKeys:
            
            SetActiveShapeKey(name)
            currentShapeKey = bpy.context.object.active_shape_key
            
            SetActiveShapeKey(shapeKeyToBeApplied_name)
            applyShapeKey = bpy.context.object.active_shape_key
            
            #Add new shapekey from mix
            O.object.shape_key_clear()
            
            currentShapeKey.value = 1.0
            applyShapeKey.value = 1.0
            
            O.object.shape_key_add(from_mix=True)
            bpy.context.object.active_shape_key.name = currentShapeKey.name + "_"
            
            
        for name in listOfKeys:
            
            #Set index to target shapekey
            SetActiveShapeKey(name)
            #Remove
            O.object.shape_key_remove(all=False)
            
            
        SetActiveShapeKey(shapeKeyToBeApplied_name)
        O.object.shape_key_remove(all=False)

        #Remove the "_" at the end of each shapeKey
        for s_key in bpy.context.object.data.shape_keys.key_blocks:
            
            s_key.name = s_key.name[:-1]
            
            
        return {'FINISHED'}




# I'm honestly not sure how to add this to an existing menu in 2.8, so rather than go
# down a rabbit-hole of research, I'm just adding a panel, because it works and is
# quick to do. Someone should probably look at this and do better than I have.
class PT_shapeKeyHelpers(bpy.types.Panel):
    """Creates a Panel in the Object properties window"""
    bl_label = "Shapekey tools"
    bl_idname = "SHAPEHELPER_PT_uipanel"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "data"


    @classmethod
    def poll(cls, context):
        return True
        return bpy.context.active_object == 'MESH'


    def draw(self, context):
        self.layout.separator()
        self.layout.operator(ShapeKeySplitter.bl_idname, text="Split Shapekeys", icon="FULLSCREEN_ENTER")
        self.layout.operator(ShapeKeyPreserver.bl_idname, text="Apply Modifiers and Keep Shapekeys", icon="MODIFIER")
        self.layout.operator(ShapeKeyApplier.bl_idname, text="Apply Selected Shapekey as Basis", icon="KEY_HLT")


classes = (
    ShapeKeySplitter,
    ShapeKeyPreserver,
    ShapeKeyApplier,
    PT_shapeKeyHelpers

)
    

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)

def unregister():
    from bpy.utils import unregister_class
    for cls in reversed(classes):
        unregister_class(cls)



if __name__ == "__main__":
    register()

I’ve only been coding in python for a couple of years, and am a rank amateur, so if I did anything dumb, please either let me know, or fix it yourself. I’m going to go ahead and submit this to the original githib repository also.

2 Likes

Thanks for taking the time to do this!

I can’t really test the new version myself right now (and I’m not much of a python coder), so I’ll just merge your changes and create a new release on github and we’ll deal with any bugs or unexpected things later. :smile:

Here’s the link, if you’re interested: https://github.com/JanOtt/ShapeKey-Helpers/releases

I’m glad to help. :slight_smile:
I don’t have time either to properly maintain it, but once I’ve used it “in the field” I will probably make another pass to fix a few quirks, like the panel still showing up when a non-mesh object is active. (I forgot the poll method), and possibly figuring out how to go back to having it in the native shape key menu.

Hey everyone!

Since a small number of people seem to still be using this addon and even improving it (by adding features, correcting some of the mistakes in my old code, […]), I wanted to ask whether someone else would like to take over the Github repository.

I have very little time these days and can’t really properly maintain this addon, mainly because

  1. I’m not a particularly skilled python coder and…
  2. I have very little experience with Github.

So if anyone wants to take over for me and continue maintaining the Github repository, that would be great! Just reply to this thread or send me a message and we’ll figure out the rest.

Cheers!

1 Like

There’s no need for anyone to take over anything, anyone can just fork it and you can link to that better fork in this thread.

Yeah, that’s probably the best solution.

In this case, if anyone wants to fork the repository, please do and send me a message and I’ll update all the links in this thread.

I so needed exactly this add on for the last month as I was working on a heart valve animation! I had solidify and subsurf on the mesh before adding the shape keys and I needed to distort the mesh after the shape keys were created (which was complicated) and it couldn’t be done without ditching the modifiers and recreating the keys! Thank you, it works fine on 2.82.7

Hey, i used to have this addon installed on 2.82 before i had to reinstall my windows… and now for some reason i can’t seem to get it to install… did you just install it the normal way using the blender UI or some super secret way i am not aware of?

Blender says it installs it correctly but it is not appearing… I really really need this add-on >_>

2 Likes

I unzipped it and installed the .py file. Hope it works for you! I am running 2.82.7

You are truly a sanity saver. lack of this function has destroyed much of my work. Thank you very much for making and sharing this! It worked exactly as i needed it to, with no hassle.

Some people may not like the lack of the usual install procedure. I just ran the script, and the buttons became available.

Be well!

1 Like

I installed and enabled it but those new functions never showed up anywhere… Blender 2.81, plugin 1.1

Please help

Ah, nevermind. Found it. It’s not located where it was on the screenshots I saw.

I can’t manage to install it.
I’ve installed like I normally install addons, Like:
Preference/Add-ons/Install Add-ons
Then I point to the zip file (ShapeKey-Helpers-1.1.zip), it says “Modules installed” at bottom of Blender but no new menu in the Shape Key panel…

I’ve tried 2.83, 2.90, 2.92 but nothing works.
I’ve tried with version 1.1

After i redirected someone to this page tonight after reading random comment on youtube. I noticed its been ported to 2.8

I could not help it finding its addition of a panel for this addon a bit weird. Why not add it to the specials context menu. I mean these actions feel like special actions, thats where those menu’s are for.
I tested it and still works.

EDIT
ah nice… now i remember again how i find it think. In the old version it was also in that menu. The panel makes no sense at all

PS if Luciano would not mind, i would add his guide to your readme. This current article doesnt have much info. And sending first from Github to here and then redirecting them to his channel is abit weird if you ask me.

EDIT 2
PS im not sure if its useful but i also just added the functionality for curves. I first adjsuted the menu so it would grey out for curves since that caused errors. Then checked what needed to be change, wasnt that much actually and it still seems to work as intened. Though i only tested apply modifiers now. Ill check the others as well

1 Like

I did some testing and ive also made the splitter work automatically. Meaning, one does not need to create 2 2 vertex groups manually, this is now all automated. Did some search for parts i could use to select vertices. Ive automated that part. So now meshes gets selected by half it adds the 2 groups and applies it.

1 Like

Hi!

If I recall correctly, the person(s) who ported the addon over to 2.8/2.9 didn’t have time (or couldn’t figure out how) to add the operators back into the specials context menu, so putting them into a dedicated panel was the best temporary solution.

Anyway, the changes you’ve suggested definitely sound like a good idea!

Are you on Github, by any chance? If so, it would be great if you could fork the current repository!
Since I haven’t worked on this addon for years now and don’t really have the Python knowledge to support it or answer questions, it is probably best if someone else “takes over” the development (or at least the Github side of things).

Just let me know and I’ll update the links in this thread and on my Github page to refer to your fork!

1 Like

Ill have a bit of python knowledge and otherwise i use Google and forums.
Ill try to update it a bit.

Here’s a quick preview of that automated splitter. Its now really split second operator. But depends on the mesh you run it with. I basically loop over all vertices with positive numbers.