8
$\begingroup$

I've spent a lot of time with creating Shape Keys for my model and now I see I have forgot to Apply the Subdivision Surface modifier.

How can I Apply modifier and keep all my Shape Keys?
Thank you for help.

$\endgroup$
5
  • $\begingroup$ where do you want to use your (well known) dragon ? In UE ? I dont know if there is a way to export a mesh with shape keys in a good format for UE ? Have you checked all that ? $\endgroup$
    – lemon
    Jun 28, 2016 at 13:07
  • $\begingroup$ Have a try with a very simple mesh with some shape keys and a subsurf. $\endgroup$
    – lemon
    Jun 28, 2016 at 13:14
  • $\begingroup$ I truly think I have posted way too many things about my dragon project indeed.. :D UE4 seems to support this indeed. Shape keys there are called morph targets. However, I haven' t tried that earlier.. With very simple mesh, like cube, it's relatively easy to push or drag the geometry when subsurf is applied, but I have an eyelid which can' t be dragged as easily, even with proportional editing it's quite hard to get the shape I look for because there's too much geometry to handle... $\endgroup$ Jun 28, 2016 at 13:30
  • $\begingroup$ FYI. I dont have UE, but I did some tests exporting and reimporting a simple object with both shape keys and subsurf. I did it in obj, and dae. Each time the result was bad : the reimported object was not good. I dont know if it is to considerer as "bad news" for you... but to be totally sure, you should import some simple mesh to UE and test it. $\endgroup$
    – lemon
    Jun 28, 2016 at 14:47
  • $\begingroup$ There is a workaround here blender.stackexchange.com/questions/9007/…. This is the same principle for subsurf and array. It works, I have tested it. $\endgroup$
    – lemon
    Jun 28, 2016 at 15:16

5 Answers 5

13
$\begingroup$

This answer comes from a previous one here Cannot Apply Array Modifier. I simply add some more details in the explanation and a way to do it for all shape keys at once.

Here is a simple shape, which has a subsurface modifier and shape keys :

enter image description here

enter image description here

Make one copy of your initial object for each shape key and set for each the shape key to "1" :

enter image description here

Select these copies and pull down the Object menu and select Convert > Mesh, (for 2.79 and earlier press Alt + C Convert > Mesh from Curve).

enter image description here

The modifiers are applied and the shape keys too (these objects now have no shape keys or subsurf modifiers).

Make another copy of your original object, remove the shape keys from it, and apply the Subsurf modifier :

enter image description here

Now select the "shape keyed" objects, and the last copy we just made (in this order), and in the shape key panel use "join as shapes" :

enter image description here

We now have the final object, with the subsurface modifier applied and the shape keys available : enter image description here

$\endgroup$
8
  • $\begingroup$ Thanks alot for this wonderful explanation. I don' t know what I would've done without your help (probably created all those 10 shape keys again...). Thanks.. man once again :) $\endgroup$ Jun 28, 2016 at 18:31
  • 1
    $\begingroup$ you are welcome. I hope to see some pic of your dragon live : ) $\endgroup$
    – lemon
    Jun 28, 2016 at 18:32
  • $\begingroup$ but anyway.. have a check about how will UE accept shape keys (after an export from Blender) $\endgroup$
    – lemon
    Jun 28, 2016 at 18:35
  • $\begingroup$ Yep. I'll let you know when I finally finish it... which seemingly won' t happen in near future if I keep doing those mistakes that I'm doing (but it's really good to go through all those mistakes by myself so I could understand how much pain they can cause :) ). Yup. I'm trying to get to know it better. I have quite many problems already with UE4 by understanding what can be done or what can't.. For example, today I spent the whole day trying to understand whether I should use one multi-texture for my dragon using UDIM technology or use 3 separated objects instead. $\endgroup$ Jun 29, 2016 at 0:41
  • $\begingroup$ Anyways. I'll let you know when the model is ready :D $\endgroup$ Jun 29, 2016 at 0:41
8
$\begingroup$

Here is an updated script which is tested and working for me on 2.91.2. The script from the existing answers still wasn't working for me in 2.9, so I took into account the fixes mentioned the comment by @paddyg (also had to remove apply_as='DATA' from modifier_apply since that is deprecated). It still creates a duplicate Basis.001 shape key (which you can just delete) but otherwise I think it works properly.

To use it, just paste this code into the Blender text editor, select your object, then click the Run Script button (little play button in the text editor header).

(I added the call to super_apply_modifiers() at the end so you could just run it from the text editor. This seemed easier to me than running it from the console, but you can remove the last line if you want to do it the other way.)

import bpy

def reset_shape_keys ():    
    for name, shape_key in get_active_block().items():
        shape_key.value = 0

def get_active_block ():
    block_id = bpy.context.object.active_shape_key.id_data.name
    return bpy.data.shape_keys[block_id].key_blocks

def select (selection):
    bpy.ops.object.select_all(action='DESELECT')
    selection.select_set(True)
    bpy.context.view_layer.objects.active = selection

def select_last_shape_key ():
    shape_key_count = len(get_active_block().items())
    bpy.context.object.active_shape_key_index = shape_key_count - 1

def remove_shape_keys (object):
    selection = bpy.context.object
    object.shape_key_clear()
    select(selection)

def apply_modifiers (object):
    selection = bpy.context.object
    select(object)

    for key, modifier in object.modifiers.items():
        if key != 'Armature':
            bpy.ops.object.modifier_apply(modifier=key)

    select(selection)        

def super_apply_modifiers ():
    original = bpy.context.object
    bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={
        "linked":False, "mode":'TRANSLATION'}, 
        TRANSFORM_OT_translate={
            "value":(0, 0, 0), 
            "constraint_axis":(False, False, False), 
            "orient_matrix":[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
            "orient_matrix_type":'GLOBAL',
            "mirror":False, 
            "use_proportional_edit":False, 
            "proportional_edit_falloff":'SMOOTH', 
            "proportional_size":1, 
            "snap":False, "snap_target":'CLOSEST', 
            "snap_point":(0, 0, 0), 
            "snap_align":False, 
            "snap_normal":(0, 0, 0), 
            "gpencil_strokes":False, 
            "texture_space":False, 
            "remove_on_cancel":False, 
            "release_confirm":False, 
            "use_accurate":False
        })
    backup = bpy.context.object
    backup.name = original.name + '_backup'
    remove_shape_keys(original)
    apply_modifiers(original)

    for key, shape_key in get_active_block().items():
        select(backup)
        bpy.ops.object.duplicate_move(
            OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, 
            TRANSFORM_OT_translate={
                "value":(0, 0, 0), 
                "constraint_axis":(False, False, False), 
                "orient_matrix":[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
                "orient_matrix_type":'GLOBAL',
                "mirror":False, 
                "use_proportional_edit":False, 
                "proportional_edit_falloff":'SMOOTH', 
                "proportional_size":1, 
                "snap":False, 
                "snap_target":'CLOSEST', 
                "snap_point":(0, 0, 0), 
                "snap_align":False, 
                "snap_normal":(0, 0, 0), 
                "gpencil_strokes":False, 
                "texture_space":False, 
                "remove_on_cancel":False, 
                "release_confirm":False, 
                "use_accurate":False
            })

        meshed_shape_key = bpy.context.object
        select(meshed_shape_key)
        reset_shape_keys()
        get_active_block()[key].value = 1
        bpy.ops.object.convert(target='MESH')

        select(original)
        meshed_shape_key.select_set(True)
        bpy.ops.object.join_shapes()
        select_last_shape_key()
        bpy.context.object.active_shape_key.name = key

        select(meshed_shape_key)
        bpy.ops.object.delete(use_global=False)

super_apply_modifiers()

Thank you everyone for the other answers, I had done a bunch of modeling using multiple shape keys with a subsurf modifier on the whole time, without realizing this isn't the "proper" workflow, and then got slapped with the "modifier cannot be applied to a mesh with shape keys" error.

EDIT: When I first posted this, I'd only tested in 2.90.1. It broke again in 2.91.2, but after updating remove_shape_keys() to use object.shape_key_clear() instead of iterating, as suggested by @paddyg, it works again.

EDIT by vklidu: I thought @Ned script is outdated, but the script works even in 3.3.

If there is set just a one Shape Key it works fine ... if there is more Shape Keys, than "Key 1" has to be selected (under Data Properties > Shape Keys) to prevent error.

$\endgroup$
4
  • $\begingroup$ Thank you! I was trying to get the ShapeKeys Helper plugin working for this and it wouldn't work with 2.80+, but this worked great! $\endgroup$
    – Ecksters
    Mar 26, 2021 at 5:42
  • $\begingroup$ Does this un-intuitive behaviour of "needed selection" can be avoided by some script update? Thank you $\endgroup$
    – vklidu
    Jun 5, 2022 at 12:13
  • $\begingroup$ Any update on this for 3.3? -- It fails with the following error: ERROR (bke.mesh_convert): C:\Users\blender\git\blender-v330\blender.git\source\blender\blenkernel\intern\mesh_convert.cc:1574 BKE_mesh_nomain_to_mesh: YEEK! this should be recoded! Shape key loss!: ID 'MECube.003'. $\endgroup$ Oct 17, 2022 at 16:20
  • $\begingroup$ Oh -- figured it out -- two issues: 1.) I had to delete the shape key's driver. (The script isn't smart enough to preserve those -- so you will still have to recreate them all -- any chance for a fix?) -- and 2.) contrary to the instructions, don't create a key named "Key 1", just select the first non-"Basis" key. $\endgroup$ Oct 17, 2022 at 16:31
4
$\begingroup$

I scripted @Lemon 's answer!

the code is kinda long, but I think it is (I hope it to be) self-explanatory. Just copy the code into a new file on blender's text editor, call the text file apply_with_shape_keys.py, and then, select the object with the modifiers and shape keys, (make a backup, just in case). Now from the console execute it like this:

>>> import apply_with_shape_keys
>>> apply_with_shape_keys.super_apply_modifiers()

the script will apply all the modifiers conserving the shape keys! and will create a new object called "backup" without the modifiers applied (just in case you wanna add new shape keys in the future).

If the object has an armature, the script will explicitly not apply that modifier. Also you have to make sure that the skeleton is in the original pose, otherwise strange things will happen.

here is the code!

import bpy

def reset_shape_keys ():    
    for name, shape_key in get_active_block().items():
        shape_key.value = 0

def get_active_block ():
    block_id = bpy.context.object.active_shape_key.id_data.name
    return bpy.data.shape_keys[block_id].key_blocks

def select (selection):
    bpy.ops.object.select_all(action='DESELECT')
    selection.select = True
    bpy.context.scene.objects.active = selection

def select_last_shape_key ():
    shape_key_count = len(get_active_block().items())
    bpy.context.object.active_shape_key_index = shape_key_count - 1

def remove_shape_keys (object):
    selection = bpy.context.object
    select(object)
    shape_key_count = len(get_active_block().items())
    select_last_shape_key()
    for i in range(0, shape_key_count):
        bpy.ops.object.shape_key_remove(all=False)
    select(selection)

def apply_modifiers (object):
    selection = bpy.context.object
    select(object)

    for key, modifier in object.modifiers.items():
        if key != 'Armature':
            bpy.ops.object.modifier_apply(apply_as='DATA', modifier=key)

    select(selection)        

def super_apply_modifiers ():
    original = bpy.context.object
    bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False})
    backup = bpy.context.object
    backup.name = 'backup'
    remove_shape_keys(original)
    apply_modifiers(original)

    for key, shape_key in get_active_block().items():
        select(backup)
        bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False})

        meshed_shape_key = bpy.context.object
        select(meshed_shape_key)
        reset_shape_keys()
        get_active_block()[key].value = 1
        bpy.ops.object.convert(target='MESH')

        select(original)
        meshed_shape_key.select = True
        bpy.ops.object.join_shapes()
        select_last_shape_key()
        bpy.context.object.active_shape_key.name = key

        select(meshed_shape_key)
        bpy.ops.object.delete(use_global=False)
$\endgroup$
3
  • 1
    $\begingroup$ I've made a simple change to your code, to give the backup object a more comprehensive name instead of just "backup" by adding "_backup" to the original file name.>>>backup.name = original.name +'_backup' $\endgroup$
    – Ahmed Ali
    Sep 15, 2018 at 23:25
  • $\begingroup$ Throws error: keyword "constraint_orientation" unrecognized. :( $\endgroup$
    – BuzzKirill
    Jun 2, 2020 at 6:09
  • $\begingroup$ what version of blender are you using? I made it a long time ago lol... are you with blender 2.8+? $\endgroup$ Jul 1, 2020 at 18:42
3
$\begingroup$

I just updated the @Bengalaa 's script with @Lemon 's answer to work in blender 2.90

# File: apply_with_shape_keys.py
# usage:
#   - select an object to apply modifiers on
#   >>> import apply_with_shape_keys
#   >>> apply_with_shape_keys.super_apply_modifiers()

import bpy

def reset_shape_keys ():    
    for name, shape_key in get_active_block().items():
        shape_key.value = 0

def get_active_block ():
    block_id = bpy.context.object.active_shape_key.id_data.name
    return bpy.data.shape_keys[block_id].key_blocks

def select (selection):
    bpy.ops.object.select_all(action='DESELECT')
    selection.select = True
    context.view_layer.objects.active = selection

def select_last_shape_key ():
    shape_key_count = len(get_active_block().items())
    bpy.context.object.active_shape_key_index = shape_key_count - 1

def remove_shape_keys (object):
    selection = bpy.context.object
    select(object)
    shape_key_count = len(get_active_block().items())
    select_last_shape_key()
    for i in range(0, shape_key_count):
        bpy.ops.object.shape_key_remove(all=False)
    select(selection)

def apply_modifiers (object):
    selection = bpy.context.object
    select(object)

    for key, modifier in object.modifiers.items():
        if key != 'Armature':
            bpy.ops.object.modifier_apply(apply_as='DATA', modifier=key)

    select(selection)        

def super_apply_modifiers ():
    original = bpy.context.object
    bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={
        "linked":False, "mode":'TRANSLATION'}, 
        TRANSFORM_OT_translate={
            "value":(0, 0, 0), 
            "constraint_axis":(False, False, False), 
            "orient_matrix":[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
            "orient_matrix_type":'GLOBAL',
            "mirror":False, 
            "use_proportional_edit":False, 
            "proportional_edit_falloff":'SMOOTH', 
            "proportional_size":1, 
            "snap":False, "snap_target":'CLOSEST', 
            "snap_point":(0, 0, 0), 
            "snap_align":False, 
            "snap_normal":(0, 0, 0), 
            "gpencil_strokes":False, 
            "texture_space":False, 
            "remove_on_cancel":False, 
            "release_confirm":False, 
            "use_accurate":False
        })
    backup = bpy.context.object
    backup.name = 'backup'
    remove_shape_keys(original)
    apply_modifiers(original)

    for key, shape_key in get_active_block().items():
        select(backup)
        bpy.ops.object.duplicate_move(
            OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, 
            TRANSFORM_OT_translate={
                "value":(0, 0, 0), 
                "constraint_axis":(False, False, False), 
                "orient_matrix":[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
                "orient_matrix_type":'GLOBAL',
                "mirror":False, 
                "use_proportional_edit":False, 
                "proportional_edit_falloff":'SMOOTH', 
                "proportional_size":1, 
                "snap":False, 
                "snap_target":'CLOSEST', 
                "snap_point":(0, 0, 0), 
                "snap_align":False, 
                "snap_normal":(0, 0, 0), 
                "gpencil_strokes":False, 
                "texture_space":False, 
                "remove_on_cancel":False, 
                "release_confirm":False, 
                "use_accurate":False
            })

        meshed_shape_key = bpy.context.object
        select(meshed_shape_key)
        reset_shape_keys()
        get_active_block()[key].value = 1
        bpy.ops.object.convert(target='MESH')

        select(original)
        meshed_shape_key.select = True
        bpy.ops.object.join_shapes()
        select_last_shape_key()
        bpy.context.object.active_shape_key.name = key

        select(meshed_shape_key)
        bpy.ops.object.delete(use_global=False)
$\endgroup$
1
  • 1
    $\begingroup$ Thanks to all above for helpful answers. I still got errors running with Blender 2.90.1-linux64 essentially due to object.select = True which had to be object.select_set(True) and the bpy.context.object.active_shape_key.id_data.name caused errors for some reason running in the Text editor. The armature modifier also got deleted but easy enough to put back (and an additional shape_key Basis was added) Finally I used object.shape_key_clear() to delete all the shape_keys rather than iterating. $\endgroup$
    – paddyg
    Dec 30, 2020 at 22:47
1
$\begingroup$

Here is fixed version.

Tested with Blender 3.3.1

Edit: copy pasted code from my gist

# https://blender.stackexchange.com/questions/56795/script-outdated-shape-keys-and-applying-subdivision-surface-modifier
import bpy

def reset_shape_keys ():    
    for name, shape_key in get_active_block().items():
        shape_key.value = 0

def get_active_block ():
    if bpy.context.object.active_shape_key is None or bpy.context.object.active_shape_key.id_data is None:
        bpy.context.object.active_shape_key_index = 0
    block_id = bpy.context.object.active_shape_key.id_data.name
    return bpy.data.shape_keys[block_id].key_blocks

def select (selection):
    bpy.ops.object.select_all(action='DESELECT')
    selection.select_set(True)
    bpy.context.view_layer.objects.active = selection

def select_last_shape_key ():
    shape_key_count = len(get_active_block().items())
    bpy.context.object.active_shape_key_index = shape_key_count - 1

def remove_shape_keys (object):
    selection = bpy.context.object
    object.shape_key_clear()
    select(selection)

def apply_modifiers (object):
    selection = bpy.context.object
    select(object)

    for key, modifier in object.modifiers.items():
        if key != 'Armature':
            bpy.ops.object.modifier_apply(modifier=key)

    select(selection)        

def super_apply_modifiers ():
    original = bpy.context.object
    bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={
        "linked":False, "mode":'TRANSLATION'}, 
        TRANSFORM_OT_translate={
            "value":(0, 0, 0), 
            "constraint_axis":(False, False, False), 
            "orient_matrix":[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
            "orient_matrix_type":'GLOBAL',
            "mirror":False, 
            "use_proportional_edit":False, 
            "proportional_edit_falloff":'SMOOTH', 
            "proportional_size":1, 
            "snap":False, "snap_target":'CLOSEST', 
            "snap_point":(0, 0, 0), 
            "snap_align":False, 
            "snap_normal":(0, 0, 0), 
            "gpencil_strokes":False, 
            "texture_space":False, 
            "remove_on_cancel":False, 
            "release_confirm":False, 
            "use_accurate":False
        })
    
    backup = bpy.context.object
    backup.name = original.name + '_backup'
    remove_shape_keys(original)
    apply_modifiers(original)

    try:
        for key, shape_key in get_active_block().items():
            select(backup)
            if(key == "Basis"):
                continue
            bpy.ops.object.duplicate_move(
                OBJECT_OT_duplicate={"linked":False, "mode":'TRANSLATION'}, 
                TRANSFORM_OT_translate={
                    "value":(0, 0, 0), 
                    "constraint_axis":(False, False, False), 
                    "orient_matrix":[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
                    "orient_matrix_type":'GLOBAL',
                    "mirror":False, 
                    "use_proportional_edit":False, 
                    "proportional_edit_falloff":'SMOOTH', 
                    "proportional_size":1, 
                    "snap":False, 
                    "snap_target":'CLOSEST', 
                    "snap_point":(0, 0, 0), 
                    "snap_align":False, 
                    "snap_normal":(0, 0, 0), 
                    "gpencil_strokes":False, 
                    "texture_space":False, 
                    "remove_on_cancel":False, 
                    "release_confirm":False, 
                    "use_accurate":False
                })
            
            print("key: " + key);
            meshed_shape_key = bpy.context.object
            meshed_shape_key.name = backup.name + '_' + key
            select(meshed_shape_key)
            reset_shape_keys()
            get_active_block()[key].value = 1
            bpy.ops.object.convert(target='MESH')

            select(original)
            meshed_shape_key.select_set(True)
            bpy.ops.object.join_shapes()
            select_last_shape_key()
            bpy.context.object.active_shape_key.name = key
            select(meshed_shape_key)
            bpy.ops.object.delete(use_global=False)
        
        backup.hide_set(True)

    except Exception as err:
        original_name = original.name
        original.name = original.name + "_broken"
        backup.name = original_name
        select(original)

super_apply_modifiers()

gist version might have other fixes too:

https://gist.github.com/terokorp/c2e0bdadf4cffcc856098b93b84d38a9/

$\endgroup$
3
  • $\begingroup$ Understandable to post a link to github, but typically we ask that the answer contain all of the necessary information and links are supplementary. Cheers and thanks for the work. $\endgroup$ Nov 25, 2022 at 0:26
  • $\begingroup$ Thanks for your contribution! However, it would be great if you could reword the answer so that all the necessary information can be found in it and it doesn't just consist of a reference to an external link. $\endgroup$
    – quellenform
    Nov 25, 2022 at 1:17
  • $\begingroup$ Thank you, that is an improvement, I undelete the post again! In the future, please always make sure to give the most comprehensive answer possible, because content to external links can be lost, which would make your post here useless. $\endgroup$
    – quellenform
    Nov 25, 2022 at 15:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .