3

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

akerblom
  • 33
  • 3
  • 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 – batFINGER Aug 22 '18 at 13:27
  • 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. – akerblom Aug 23 '18 at 12:51
  • 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. – batFINGER Aug 23 '18 at 18:44
  • 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. – akerblom Aug 27 '18 at 06:07

2 Answers2

2

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)

batFINGER
  • 84,216
  • 10
  • 108
  • 233
  • This suggestion solves my problem and is thus marked as accepted. However, it does not use bmesh as requested. Still much appreciated! – akerblom Aug 27 '18 at 06:09
0

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"

Cardboy0
  • 159
  • 8