Ohh, swapping the data, of course! @Cirno, you’re a genius! Thank you!
One of the things my script (and future stinky but freebie addon) does is duplicate keys. The way you’re handling the data is way more elegant than what I was doing since I’m not an actual coder, so I think I’ll steal this for the duplicate function as well. 
Here’s what I did with the data swap code (besides uglifying it!): Noodled a snippet to copy properties (values, mute key etc) too, sneaked into the duplicate function and used to position dup keys before/after their sources. I’m halfway writing the functions to copy drivers and actions for the sake of completeness, but the code is still messy so I left it out here.
import bpy
import numpy as np
c = bpy.context
ob = c.object
me = ob.data
skeys = me.shape_keys
### Shape Key functions
def shape_key_swap_vertices(key_name, index, shape_keys=skeys, me=me):
"""Swap vertices and properties between key1 and key2"""
verts = me.vertices
c = len(verts)
key1 = skeys.key_blocks[key_name]
key2 = skeys.key_blocks[index]
key1_data = np.zeros(c*3, dtype=np.float32)
key1.data.foreach_get("co", key1_data.ravel())
key2_data = np.zeros(c*3, dtype=np.float32)
key2.data.foreach_get("co", key2_data.ravel())
key1.data.foreach_set("co", key2_data)
key2.data.foreach_set("co", key1_data)
shape_key_clone_props(key1, key2, swap=True)
# TODO: Drivers, animation support
def shape_key_clone_props(source_key, target_key, swap=False):
"""Copy shape source_key proprieties to target_key, optitionally swapping between them"""
if swap:
key_temp = {
"source_name" : source_key.name,
"name" : target_key.name,
"value" : target_key.value,
"mute" : target_key.mute,
"vertex_group" : target_key.vertex_group,
"relative_key" : target_key.relative_key,
"slider_min" : target_key.slider_min,
"slider_max" : target_key.slider_max,
}
target_key.name = source_key.name
target_key.value = source_key.value
target_key.mute = source_key.mute
target_key.vertex_group = source_key.vertex_group
target_key.relative_key = source_key.relative_key
target_key.slider_min = source_key.slider_min
target_key.slider_max = source_key.slider_max
if swap:
source_key.name = key_temp["name"]
source_key.value = key_temp["value"]
source_key.mute = key_temp["mute"]
source_key.vertex_group = key_temp["vertex_group"]
source_key.relative_key = key_temp["relative_key"]
source_key.slider_min = key_temp["slider_min"]
source_key.slider_max = key_temp["slider_max"]
target_key.name = key_temp["source_name"]
def shape_key_duplicate(key_name, object=ob, shape_keys=skeys, me=me):
"""Copy vertices and properties from source_key to target_key"""
verts = me.vertices
c = len(verts)
bpy.data.objects[object.name].shape_key_add(name=key_name, from_mix=False) # TODO: Preferences option for naming convention
source_key = skeys.key_blocks[key_name]
source_key_idx = skeys.key_blocks.keys().index(key_name)
target_key = skeys.key_blocks[-1]
# Clone vertex coordinates
source_key_data = np.zeros(c*3, dtype=np.float32)
source_key.data.foreach_get("co", source_key_data.ravel())
target_key.data.foreach_set("co", source_key_data)
# Clone attributes
shape_key_clone_props(source_key, target_key)
shape_key_reorder(source_key_idx, target_key=target_key, after=True) # TODO: Preferences option for reordering
# TODO: Drivers, animation support
def shape_key_reorder(destination_idx, object=ob, shape_keys=skeys, target_key=ob.active_shape_key, after=False):
"""Reorder Shape Key list, positioning target key at or after destination_idx"""
target_key_name = target_key.name
target_key_idx = skeys.key_blocks.keys().index(target_key_name)
travel = destination_idx
if after:
travel += 1
activate = travel
for key in range(destination_idx, target_key_idx):
new_target_name = skeys.key_blocks[target_key_idx].name
shape_key_swap_vertices(new_target_name, travel)
travel += 1
object.active_shape_key_index = activate
### RUN!
target_key = ob.active_shape_key
shape_key_duplicate(target_key.name, ob, skeys)