Starting from this code:
import bpy
scene = bpy.context.scene
for o in bpy.context.selected_objects:
if o.type == "MESH":
if o.data.shape_keys:
for k in o.data.shape_keys.key_blocks:
ojs = bpy.data.objects
bpy.context.view_layer.objects.active = o #set context.object
o.shape_key_add(name='CombinedKeys', from_mix=True)
if o.data.shape_keys:
for shapeKey in o.data.shape_keys.key_blocks:
o.shape_key_remove(shapeKey)
A few things could be tweaked:
Setting the active object
It isn't even needed for your script to work. But if you did something else that required it, it should definitely happen in the object loop, not in the keys loop. Otherwise, you would set the same object active multiple times for nothing.
For and If statement
Anytime you have a For followed by IF, you could simplify it. I.E:
for o in bpy.context.selected_objects:
if o.type == "MESH":
if o.data.shape_keys:
Can be simplified into:
for o in (o for o in bpy.context.selected_objects if o.type == 'MESH' and o.data.shape_keys):
Making a combined shapekey
For some reason, you make a new shape from mix. But your current code have this happen as many times as there are shape in your object. This sounds quite tedious. Plus, this function works with the "current visual state" of the mesh. Meaning, if you have all your shape keys influences at zero, the shape from mix will be exactly as your base shape.
This needs better thinking, what is your goal with that? To preserve all the additional shape keys as one shape? Does it need to include the shapes named as "face"? Does the script have to turn all shape keys influence to 1 first?
In addition, your current script deletes that shape key anyway, so it's effectively useless.
In my revision, I will assume you want to create a new mixed shapekey to combine all the ones we delete.
Since we will need to make operations on either filtered in or filtered out object alternatively, I will make a list of them instead of always making for loops with extra filters all the time. It will make the code more readable, easier to manage by having only one place to handle filtering, allows you to easily do more operation on either lists of objects, and also I think it would be more performant (I think, I'm no expert).
shapes_to_delete, shapes_to_preserve = [], []
for shape in o.data.shape_keys.key_blocks:(
shapes_to_delete if 'face' not in shape.name.lower() and 'basis' not in shape.name.lower()
else shapes_to_preserve
).append(shape)
Note that I included the "basis" shape in the list of shapes to preserve. Since this is essentially your "original" mesh on which any shapekey is usually based on, you really need to keep it to not break your object.
Also note that I matched the names with name.lower()
instead of just name
. The former allows to not worry about capitalization. If you want to have a case-sensitive matching, use name
instead.
Final procedures:
All we need to do is:
- set all to-preserve keys' values to zero
- set all to-preserve keys' values to 1
- create new shape from mix
- add the new shape to list of to preserve (if you want to do more operation on them later on)
- delete all to-delete shapes
These can be done quite simply with a few For loops.
Final result:
import bpy
for o in (o for o in bpy.context.selected_objects if o.type == 'MESH' and o.data.shape_keys):
print(f"Found mesh object with shapekey data: {o.name}")
# bpy.context.view_layer.objects.active = o # not needed here, but just in case, that's where you'd put it.
shapes_to_delete, shapes_to_preserve = [], []
for shape in o.data.shape_keys.key_blocks:(
shapes_to_delete if 'face' not in shape.name.lower() and 'basis' not in shape.name.lower()
else shapes_to_preserve
).append(shape)
print(" Keys to preserve:")
for shape in shapes_to_preserve:
print(f" {shape.name} of value: {shape.value}")
shape.value = 0
print(" Keys to delete:")
for shape in shapes_to_delete:
print(f" {shape.name}")
shape.value = 1
new_shape = o.shape_key_add(name='CombinedKeys', from_mix=True)
shapes_to_preserve.append(new_shape)
for k in shapes_to_delete:
o.shape_key_remove(k)