5
$\begingroup$

I have been creating a dragon model lately (it might come familiar to someone) and I've done a lot of work so far, I've also spent alot of time with creating shape keys for my eye. However, I see now that I have forgot to apply the subdivision surface modifier for my eye and when I remove the modifier it looks totally crappy.

I'm very-very confused now as I've looked help in Youtube and I've seen that almost everyone create their shape keys with subdivison surface modifier on (without having applied it earlier). I'm clueless how can this be, do they later on somehow do some kind of workaround to apply the subdivision modifier??

Am I really supposed to create shape keys for my eyelid or eyepupil when I have some crazy polycount (it's very hard to form the shapes I had previously).

I'm a bit in panic, so please pardon me. Maybe you could offer some ways to get through this :)

$\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 '16 at 13:07
  • $\begingroup$ Have a try with a very simple mesh with some shape keys and a subsurf. $\endgroup$ – lemon Jun 28 '16 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$ – TheLast_BlenderBender Jun 28 '16 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 '16 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 '16 at 15:16
9
$\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 this copies and Alt + C and "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$ – TheLast_BlenderBender Jun 28 '16 at 18:31
  • 1
    $\begingroup$ you are welcome. I hope to see some pic of your dragon live : ) $\endgroup$ – lemon Jun 28 '16 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 '16 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$ – TheLast_BlenderBender Jun 29 '16 at 0:41
  • $\begingroup$ Anyways. I'll let you know when the model is ready :D $\endgroup$ – TheLast_BlenderBender Jun 29 '16 at 0:41
5
$\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.

$\endgroup$
1
  • $\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 at 5:42
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 '18 at 23:25
  • $\begingroup$ Throws error: keyword "constraint_orientation" unrecognized. :( $\endgroup$ – BuzzKirill Jun 2 '20 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$ – pr00thmatic Jul 1 '20 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 '20 at 22:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.