3
$\begingroup$

I'm using a python script to generate mesh geometry and to add multiple shape keys in Blender using bmesh. The goal is to have 10 shape keys and each generated vertex is modified by just one of those shape keys.

My first attempt was as follows:

import bpy
import bmesh

# Create mesh.
me = bpy.data.meshes.new('TestMesh')

# Create object.
ob = bpy.data.objects.new('TestObject',me)

# Bmesh.
bm = bmesh.new()

# Add default shape key.
ob.shape_key_add('Basis')

# List of shape keys to loop through after creation.
ShapeKeys = []

# Base name for shape keys.
KeyBaseStr = 'Shape'

# Create 20 vertices.
for iVert in range(1,21):

    # Index of shape key to add the vertex to.
    iGroup = (iVert-1) % 10

    # Check if group shape key has already been created.
    if len(ShapeKeys) < (iGroup + 1):

        # Name of new shape key.
        SetName = KeyBaseStr + '_Set_' + str(iVert)

        # Create shape key and append to list.
        ShapeKeys.append(bm.verts.layers.shape.new(SetName))

        # Shorthand for new key.
        sk = ShapeKeys[-1]

        print('created shape key #',len(ShapeKeys))
    else:
        # Select existing shape key.
        sk = ShapeKeys[iGroup]

        print('using existing shape key #',iGroup+1)

    # Add vertex.
    bv = bm.verts.new(tuple([iVert, 0, 1]))

    # Assign vertex coordinates in selected shape key.
    bv[sk] = tuple([0, iVert, 2])

# Bind bmesh to mesh.
bm.to_mesh(me)  

# Link object to scene.
scene = bpy.context.scene
scene.objects.link(ob)

# Set as active and selected.
scene.objects.active = ob
ob.select = True

However, running the code results in an object with just one shape key rather than 10. All ten still appear to be created according to what gets printed and the ShapeKey list indeed seems to hold 10 BMLayerItem objects, but seem to not get binded to the mesh or the object. I also tried the same approach but created all of the shape keys layers before any vertices, but it made no difference.

The question is simple, how to add multiple shape keys while also generating geometry one vertex at a time, preferably using bmesh?

Also asked this question on BlenderArtists but received no answers so far:

https://blenderartists.org/t/problem-adding-multiple-shape-keys-with-bmesh/1122506

$\endgroup$
4
  • $\begingroup$ Not sure there is any benefit in adding a new shapkey for a new vertex. The shapekey basically stores a location for all verts in the mesh. Would use a create mesh, create and manipulate shapekey data approach.. Example here $\endgroup$
    – batFINGER
    Aug 22, 2018 at 13:27
  • $\begingroup$ I see your point in general, but in the real application there will be 10,000 to 100,000 vertices and still just 10 shape keys. Thus making the vertex-shape key ratio a lot larger than the 2:1 in this simplified example. $\endgroup$
    – akerblom
    Aug 23, 2018 at 12:51
  • $\begingroup$ Not sure I see your point. For that many verts code above will be testing 100,000 times to see if there are 10 vertex groups that are created with the first 10 iterations? Added answer based on example posted in comment above. $\endgroup$
    – batFINGER
    Aug 23, 2018 at 18:44
  • $\begingroup$ I thought your problem was with the inefficiency of having so many shape keys compared to the number of vertices, but yes, it is certainly not the most efficient to check if a shape key exists inside the for-loop. However as stated in the question, the bmesh approach had the same result even when all the shape keys were created before the vertex creation loop. I'm very interested as to what goes wrong when using bmesh to create the shape keys. $\endgroup$
    – akerblom
    Aug 27, 2018 at 6:07

2 Answers 2

2
$\begingroup$

Alternative Route

  • Not using bmesh verts shape layers.

  • Created verts with a list comprehension, then reiterated over verts using the vertex index to index which shapekey layer data.

  • Pre-made the 10 shapekeys. Each key is a data layer on the verts that holds the shapekey location. The default if not set is the

Test script.

import bpy
import bmesh
from math import ceil
from bpy import context
N = 20 # total number of verts
g = 10 # verts per sk

scene = context.scene
me = bpy.data.meshes.new("Squircle")
obj = bpy.data.objects.new("Squircle", me)

bm = bmesh.new()
verts = [bm.verts.new((i, 0, 1)) for i in range(N)]
sk = obj.shape_key_add(name="Basis") 
# make shapekeys
sks = [obj.shape_key_add(name="SK_%02d" % i) for i in range(g)]

bm.to_mesh(me)

for v in bm.verts:
    sks[v.index % g].data[v.index].co = (0, v.index, 2)
scene.collection.objects.link(obj)
$\endgroup$
1
  • $\begingroup$ This suggestion solves my problem and is thus marked as accepted. However, it does not use bmesh as requested. Still much appreciated! $\endgroup$
    – akerblom
    Aug 27, 2018 at 6:09
0
$\begingroup$

I managed to figure out how to solve your problem with bmesh only being able to create one single shapekey, because I stumbled about the same problem. Basically the solution is:

  • Don't create new Shapekeys in the bmesh and edit them
  • Instead, create them before, and then edit them in the bmesh

Example: This doesn't work, because we create the shapekeys in the bmesh

obj.shape_key_add(name="Basis", from_mix=False)
#Adding Basis shapekey

bm = bmesh.new() 
bm.from_mesh(me)

#creating new shapekey IN the bmesh
newKey1 = bm.verts.layers.shape.new("Key 1")
newKey2 = bm.verts.layers.shape.new("Key 2")
newKey3 = bm.verts.layers.shape.new("Key 3")

#changing the shapekey vertex coordinates
for vert in bm.verts:
    for SKey in (newKey1,newKey2,newKey3):
        #change vert coordinates, like:
        vert[SKey] = vert.co*2
        

#back to the real mesh
bm.to_mesh(me)

print(len(me.shape_keys.key_blocks)) # == 2
#mesh will only have two shapekeys, "Basis" and "Key 1"

Whereas this will work, because we have created them before for the original mesh:

obj.shape_key_add(name="Basis", from_mix=False)
#Adding Basis shapekey

newKey1 = obj.shape_key_add(name="Key 1", from_mix=False)
newKey2 = obj.shape_key_add(name="Key 2", from_mix=False)
newKey3 = obj.shape_key_add(name="Key 3", from_mix=False)
#creating the shapekeys BEFORE the bmesh, for the original mesh


bm = bmesh.new() 
bm.from_mesh(me)

#getting the shapekeys we already created
existingKey1 = bm.verts.layers.shape["Key 1"]
existingKey2 = bm.verts.layers.shape["Key 2"]
existingKey3 = bm.verts.layers.shape["Key 3"]

#changing the shapekey vertex coordinates
for vert in bm.verts:
    for SKey in (existingKey1,existingKey2,existingKey3):
        #change vert coordinates, like:
        vert[SKey] = vert.co*2
        

#back to the real mesh
bm.to_mesh(me)

print(len(me.shape_keys.key_blocks)) # == 4
#mesh will have all four shapekeys, "Basis", "Key 1", "Key 2" and "Key 3"
$\endgroup$
1
  • $\begingroup$ I feel like this answer is similar to the one of batFINGER, but not quite, so I decided to add it. $\endgroup$
    – Cardboy0
    Nov 21, 2021 at 12:19

You must log in to answer this question.

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