Object manipulation and removal during animation

I want to make animation in blender 2.8 by using premade stl files. I do that by importing the stls frame by frame. See code below.

In order to save memory I delete the old object before I load a new stl. This goes well for a few frames. Then a EXCEPTION_ACCESS_VIOLATION error is thrown during the Dependency Graph evaluation.

I guess I’m not deleting objects at the best time. Any ideas when to delete the old objects in my usecase? Maybe I should delete objects using another of the bpy.app.handlers?

def doatnewframe(scene):
surfacename=‘isoSurfaceTopo’

# delete old object created at previous frame
for o in bpy.data.objects:
	if surfacename in o.name:
		o.parent = None
		bpy.data.objects.remove(o, do_unlink=True,do_ui_user=True)

# import new object from stl for current frame
timedir = scene.frame_current
stlfilename=os.path.join(bpy.context.scene["datadir"],timedir,surfacename+'.stl')
bpy.ops.import_mesh.stl(filepath=stlfilename)

# get object with newly loaded stl
obnew=[o for o in bpy.context.scene.objects if o.select_get()][0] 

# adjust position of newly imported stl
origo=bpy.data.objects['rocketparent']
obnew.parent = origo
obnew.matrix_world = mathutils.Matrix.Translation([0,0,1.5]) @ origo.matrix_world

# link newly loaded object to scene
bpy.context.scene.collection.objects.link(obnew)

head,tail = os.path.split(bpy.data.filepath)
bpy.context.scene[“datadir”] = os.path.join(head,‘isoSurface’)
bpy.context.scene[“timedirs”] = next(os.walk(bpy.context.scene[“datadir”]))[1]

bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(doatnewframe)

I think handlers are a bad choice for modifying the scene’s data- it’s really best to avoid using them altogether if you can.

Here’s an addon that does something similar to what you’re doing, but with OBJs. https://github.com/neverhood311/Stop-motion-OBJ

Maybe you can adapt the code to do what you need?

Thanks for the suggestion. The Stop-motion code is somewhat similar to what I want to do. Unfortunately the code doesn’t deal with removing objects. It seems to load all stl frames into memory. I can easily to the same with my current method but would run out of memory.

Handlers work fine.
I think the problem is that you are only deleting the object but not the mesh.

bpy.data.meshes.remove(YourOldMesh)

should do the trick.

Hi Lumpengnom,
Good suggestion, but I did include mesh removal. Still same problem.
Code snippet is:

for o in bpy.data.objects:
if surfacename in o.name:
o.parent = None
bpy.data.objects.remove(o, do_unlink=True,do_ui_user=True)

for m in bpy.data.meshes:
if surfacename in m.name:
bpy.data.meshes.remove(m)

BR
Jens

You might be getting in trouble due to removing an object from bpy.data.objects while also iterating over the same list. Try doing it in two phases: 1) go through bpy.data.objects to make a separate list R of objects to remove, 2) iterate over R and delete each of the objects.

Yes, or just slice like this:

for o in bpy.data.objects[::]:

1 Like

I encountered the same crashes with your code, but I realized that the crashes only occured during renders, whereas the code worked flawlessly when playing the animation in the timeline. There seems to be a threading problem in Blender when previewing the scene while rendering, so activating “Lock Object Modes” and rendering internally instead of opening a separate window (in the preferences) solved the issue for me. I furthermore edited your code to keep the selection when switchign frames (as otherwise the import gets selected deselecting previous selections):

import bpy
import os

## Get Directory
head,tail=os.path.split(bpy.data.filepath)
bpy.context.scene['datadir']=os.path.join(head,"Frame_STL")
bpy.context.scene['prevframe']=0 

print('------------------------------------------------------------------')
## Define the object exchange script
def doatnewframe(scene):
    obj_name2='Tesst Frame ' #Objects are named with capital letters at the beginning of words and without underscores
    obj_name='test_frame_'
    max_frame=124    #Last frame that gets an exchanged model
    tot_ang=math.pi

    last_frame=bpy.context.scene['prevframe']
    curr_time=bpy.context.scene.frame_current
    timedir=min(curr_time,max_frame)
    timedir=max(1,timedir)
    bpy.context.scene['prevframe']=timedir


    if timedir!=last_frame:
        #Selected objects
        prev_sel=bpy.context.selected_objects
        prev_selected=[]
        for ps in prev_sel:
            prev_selected.append(ps.name)
        found_list=-1
        for i in range(0,len(prev_selected)):
            s=prev_selected[i]
            if obj_name2 in s:
                found_list=i
        add_new=0
        if found_list>=0:
            prev_selected.pop(found_list)
            add_new=1


        # delete old object created at previous frame
        for o in bpy.data.objects[::]:
            if obj_name2 in o.name: 
                o.parent=None
                bpy.data.objects.remove(o,do_unlink=True,do_ui_user=True)


        for m in bpy.data.meshes[::]:
            if obj_name2 in m.name:
                bpy.data.meshes.remove(m,do_unlink=True,do_ui_user=True)

        # import new object from stl for current frame
        stlfilename=os.path.join(bpy.context.scene['datadir'],obj_name+str(timedir)+'.stl')
        bpy.ops.import_mesh.stl(filepath=stlfilename)

        # get object with newly loaded stl
        new_ob_name=obj_name2+str(timedir)
        ob_new=bpy.data.objects[new_ob_name] 
        ob_new.location=(0,0,2.6)
        if timedir<max_frame:
            z_rot=tot_ang*(1-math.cos(math.pi/2*timedir/max_frame)**2)
            scaler=0.2+0.8*math.sin(math.pi/2*timedir/max_frame)
        else:
            z_rot=tot_ang
            scaler=1

        ob_new.rotation_euler=(0,0,z_rot)
        ob_new.scale=(0.25*scaler,0.25*scaler,0.25*scaler)

        # link newly loaded object to scene
        bpy.context.scene.collection.objects.link(ob_new)

        # Add Material to new object
        # Get material
        mat=bpy.data.materials.get("Silver")
        # Assign it to object
        if ob_new.data.materials:
            # assign to 1st material slot
            ob_new.data.materials[0] = mat
        else:
            # no slots
            ob_new.data.materials.append(mat)

        for poly in ob_new.data.polygons:
            poly.use_smooth = True

        # Select object
        for p in prev_selected:
            bpy.data.objects[p].select_set(True)
        if add_new==1:
            bpy.data.objects[new_ob_name].select_set(True)
        else:
            bpy.data.objects[new_ob_name].select_set(False)

    print("Changed Objects for Frame:0 ", curr_time)

bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(doatnewframe)
doatnewframe(bpy)

Hi Ntropic,
I change the settings from Render in “New Window” to Render in “Keep User Interface”. Lock Object Modes was already enabled. I still got the crash.
Your idea sparked an idea to try run it without GUI. I tried this idea on the bug report I made back in december, https://developer.blender.org/T72691.

blender -b A.blend --python-text stlbyframe.py -a > A.log

The result was a succes because blender did not crash.

1 Like

@jdk @Josephbburg
I’m working on the next version of the Stop Motion OBJ addon, which is just about to enter beta testing. I’ve added a feature called Streaming sequences that imports meshes during the animation. There’s also a mesh cache and the add-on will automatically remove meshes once the cache fills up. If you still need this kind of functionality, I think it’d be worth a try for you.

Grab the latest version of the script (which is still an alpha version, so be warned) here: https://github.com/neverhood311/Stop-motion-OBJ/releases
And see the tutorial for Streaming sequences here: https://github.com/neverhood311/Stop-motion-OBJ/wiki#import-a-largelong-sequence

1 Like