2
$\begingroup$

I want to discover all vertices that are affected by a Shape Key.

I think this would involve comparing each vertex location for a key_block when its value is 0 and 1. However the following attempt doesn't produce any result:

import bpy, bmesh

shapeKeyName='Key 1'

o=bpy.context.object
#bpy.ops.object.mode_set(mode='EDIT')
m=bmesh.new()
m.from_mesh(o.data)
m.verts.ensure_lookup_table()
sk=bpy.context.object.data.shape_keys

for v in m.verts: #o.data.vertices:
    bpy.context.object.data.shape_keys.key_blocks[shapeKeyName].value=0
    co_basis = v.co
    bpy.context.object.data.shape_keys.key_blocks[shapeKeyName].value=1
    co_shape = v.co
    if co_basis != co_shape:
        print( shapeKeyName, 'affects vertex', v.index, co_basis, 'vs', co_shape )

I see at the end of the loop, key_blocks[shapeKeyName].value ends up at value 1 and the shape key is applied properly.

$\endgroup$
1
  • 1
    $\begingroup$ so basically you just want like a diff? So for example: if I have a cube with 'Basis' shape key and a 'Key 1' and only moved 2 vertices in 'Key 1', you want to print only those 2 vertices that got affected? $\endgroup$ Jan 20, 2023 at 23:14

2 Answers 2

2
$\begingroup$

The following code does almost the same thing as the previous code, but it works 7-9 times faster by getting rid of loops and working with the low-level API

250k tris:

Harry McKenzie = 0.13 s

My = 0.015 s

import bpy
import numpy as np


def get_data_from_collection(collection, attribute, size, dtype=np.float32):
    data = np.zeros(np.prod(size), dtype=dtype)
    collection.foreach_get(attribute, data)
    return data.reshape(size)


class MeshData:
    """ Dataclass which holds all mesh settings. It is used also for area lights creation """

    first_shapekey: np.array
    second_shapekey: np.array


def compare_shapekeys(other = None, basis='Basis'):
    obj = bpy.context.active_object
    keys = obj.data.shape_keys

    data = MeshData()
      
    data.first_shapekey = get_data_from_collection(keys.key_blocks[basis].data, 'co', (len(keys.key_blocks[basis].data), 3))
    data.second_shapekey = get_data_from_collection(keys.key_blocks[other].data, 'co', (len(keys.key_blocks[basis].data), 3))

    state_by_idx = np.any((data.first_shapekey != data.second_shapekey), axis=1)

#    obj.data.vertices.foreach_set('select',  state_by_idx)
    return state_by_idx

compare_shapekeys(1) # name or idx
$\endgroup$
4
  • $\begingroup$ fast, but obscure to this simple programmer! Shouldn't the first arg to compare_shapekeys be a string like 'Key 1'? $\endgroup$
    – james_t
    Jan 25, 2023 at 19:33
  • 1
    $\begingroup$ You can call the object by name, or you can call it by index (it's shorter): $\endgroup$
    – Oxicid
    Jan 25, 2023 at 20:01
  • $\begingroup$ you don't need to declare an unnecessary MeshData class. why did you include it? $\endgroup$ Jan 26, 2023 at 4:05
  • $\begingroup$ It's more compact and readable, but yes, you can do it without it. $\endgroup$
    – Oxicid
    Jan 26, 2023 at 15:22
2
$\begingroup$

You can compare the a shape key (i.e. "Key 1") to the "Basis" shape key and print out all affected vertices with the following script:

import bpy

obj = bpy.context.active_object
keys = obj.data.shape_keys

def compare_to_basis(name):
    basis = keys.key_blocks["Basis"].data
    shape = keys.key_blocks[name].data

    for idx, point in enumerate(shape):
        if point.co != basis[idx].co:
            print(shape, 'affects vertex', idx, basis[idx].co, 'vs', point.co)

compare_to_basis("Key 1")
$\endgroup$
1
  • 1
    $\begingroup$ I hope to become as smart as thee when I reach 5691 rep points, but there's no guarantee! thanks. $\endgroup$
    – james_t
    Jan 21, 2023 at 16:32

You must log in to answer this question.

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