I'd consider getting away from bmesh_from_edit_mesh
, and re-evaluating why you might need bmesh at all. This can be done with the basic Mesh structure.
Here as a function of the event handler 'pre frame change'
As you can tell, there's no switching in and out of edit mode for the purpose of getting the Bmesh from edit mode. For what you are doing here there's little gain (that I can see..) to using bmesh data. So you can use the standard Mesh data
import bpy
import bmesh
import numpy as np
## ------- part 1 --- do this once.
sig = 0.3
n_frames = 101
bpy.context.scene.frame_end = n_frames
pi = np.pi
make_ico = bpy.ops.mesh.primitive_ico_sphere_add
make_ico(subdivisions=3, size = 2.0, location = (0,0,3))
obj = bpy.context.active_object
me = obj.data
ico0 = np.array([v.co for v in me.vertices]) # get ico vertices
zmin, zmax = ico0[:,2].min(), ico0[:,2].max()
zc = np.linspace(zmin, zmax, n_frames)
ico = np.zeros_like(ico0)
data = []
for i in range(n_frames):
ico[:,:2] = (1.0 + np.exp(-(ico0[:,2] - zc[i])**2/(2.*sig**2)))[:,None] * ico0[:,:2]
ico[:,2] = ico0[:,2]
data.append(ico.copy())
## ------- part 2 ---set up frame change event handler,
def my_handler(scene):
i_frame = scene.frame_current
if not (0 <= i_frame < len(data)):
return
obj = bpy.data.objects['Icosphere'] # be explicit.
me = obj.data
for (vert, co) in zip(me.vertices, data[i_frame]):
vert.co = co
me.update()
bpy.app.handlers.frame_change_pre.append(my_handler)
A few warnings here, useful things to be aware of.
Run this script once, it will add a function to the event handler system. This function (my_handler
) will be called on each frame change (either you scrub the timeline, or hit play..to render an animation -- any kind of frame change).
If you run the script again, another function (identical content, but different function reference) is added to the event handler, be aware of this
If you change the content of the my_handler
function and run the script again it will do undesirable things, like run both versions of the my_handler
function.
So instead of running the script over and over, and not understanding why it doesn't seem to behave right, you must remove the handler before registering a new one by the same name - code defensively to avoid 'stale' handler functions.
If you don't have any additional event handlers set-up it's easy to wipe the list using some variant of
handlers = bpy.app.handlers
num_handlers = len(handlers.frame_change_pre)
del handlers.frame_change_pre[0:num_handlers]
# or simply
bpy.app.handlers.frame_change_pre.clear()
Or.. you might want to remove handlers by function name (this is useful if you have a variety of these things going on)
my_handler_list = bpy.app.handlers.frame_change_pre
fin = len(my_handler_list)
for idx, func in enumerate(reversed(my_handler_list)):
if func.__name__ == 'my_handler':
my_handler_list.pop(fin-1-idx)
# I suspect there's a neater way to remove handlers by name
# but I stopped as soon as it worked..
Maybe obvious, but worth mentioning -- if you create the icosphere, and rerun the script it will create another. You probably don't want that and need to add a check at the top ' does A exist no? then add A else use existing A '
If you need a bmesh representation you can get it from obj.data
using:
bm = bmesh.new()
bm.from_mesh(obj.data)
as shown in TextEditor -> Templates -> Python -> Bmesh Simple
To get a perfect loop
Add the pristine coordinates as a first and last member of the data list. so it looks like this
data = [pristine, modified1, modified2... modified_last, pristine]
see: https://gist.github.com/zeffii/229ac803bdad0fdc7813