3
$\begingroup$

I understand that it's possible to keyframe visibility, and that might have to be at least part of the solution. However, I intend to create many objects at once, with a script to generate them, so I can't think how to apply visibility keyframes, when I'd have so many objects which initially don't exist.

If it helps, here's the (messy) code I'm working with:

import bpy

CoList = []

def DeleteExisting():
    bpy.ops.object.select_all(action='TOGGLE')
    bpy.ops.object.delete(use_global=False)
    bpy.ops.object.select_all(action='TOGGLE')
    bpy.ops.object.delete(use_global=False)

DeleteExisting()

def Create_List(Width):
    for x in range(0,Width):
        CoList.append([x])
    for i in CoList:
        x = i[0]
        i.pop(0)
        for y in range(0,Width):
            i.append([x,y])
    return CoList

def Generate_Cubes(CoList):
    for x in CoList:
        for y in x:
            X_Value = y[0]
            Y_Value = y[1]
            while type(X_Value) == list:
                if type(X_Value) == list:
                    X_Value = X_Value[0]
                if type(Y_Value) == list:
                    Y_Value = Y_Value[0]
            bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=(X_Value, Y_Value, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
            bpy.ops.transform.resize(value=(0.25, 0.25, 0.25), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)

for X in range(1,6):
    bpy.context.scene.frame_current = X
    Width = X+2
    DeleteExisting()
    CoList = Create_List(Width)
    Generate_Cubes(CoList)
    bpy.ops.object.select_all(action='TOGGLE')
    bpy.ops.object.select_all(action='TOGGLE')

This creates a grid of cubes, which looks like this:

And it also advances the current frame, however once an object is created, it exists on all frames, regardless of any keyframes that I know of. So how would I implement this so that objects only exist on the frame which they are created on?

$\endgroup$
2
$\begingroup$

Driver based method,

driving the hide property, simply hiding a cube if its "visframe" custom property is greater than the frame number. Creates a 10 x 10 grid of cubes where only one is visible on frame 1, 2 On frame 2, ... 100 on frame 100. Drive the hide_render property to render.

import bpy
context = bpy.context
scene = context.scene
bpy.ops.mesh.primitive_cube_add()
cube = context.active_object
cube.scale *= 0.25

for i in range(10):    
    x = 2 * i    
    for j in range(10):

        cube["visframe"] = 10 * i + j        
        # add a driver
        fcurve = cube.driver_add("hide")
        driver = fcurve.driver
        var = driver.variables.new()
        var.name = "vis"
        var.targets[0].id = cube
        var.targets[0].data_path = '["visframe"]'
        # scene.frame_current is defined as frame to driver namespace
        driver.expression = "vis >= frame" # show if cube["visframe"] > frame
        y = 2 * j
        cube.location.xy = (x, y)        
        cube = cube.copy()
        scene.objects.link(cube)

Frame Change Handlers

This one uses a frame change pre handler to remove all cubes marked with a visframe custom property, and a post handler to create them in a frame by frame grid.

import bpy
context = bpy.context
scene = context.scene

def delcubes(scene):
    cubes = [c for c in scene.objects if "visframe" in c.keys()]
    for cube in cubes:
        scene.objects.unlink(cube)
        bpy.data.objects.remove(cube)

def makecubes(scene):
    w = scene.frame_current
    bpy.ops.mesh.primitive_cube_add()
    cube = context.active_object
    cube.scale *= 0.25
    grid(cube, w)

def grid(cube, w):
    for i in range(w):    
        x = 2 * i    
        for j in range(w):
            print(i, j)
            cube["visframe"] = True        
            y = 2 * j
            cube.location.xy = (x, y)        
            cube = cube.copy()
            scene.objects.link(cube)

# for testing pays to clear the handlers.
bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_post.clear()  

# set up a frame change handler.          
bpy.app.handlers.frame_change_pre.append(delcubes)
bpy.app.handlers.frame_change_post.append(makecubes)
$\endgroup$
2
$\begingroup$

Each object has two properties called hide and hide_render - the first is viewport visibility and the second is render visibility.

To use python to set a keyframe for a property you set the value, then use keyframe_insert(data_path, frame=f) to keyframe it. So if you only want to object visible on one frame, you make it visible and set a keyframe on the relevant frame, then hide it and add a keyframe for the previous and next frame.

import bpy

# clear any existing objects
for o in bpy.data.objects:
    o.hide = False
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

grid_size = 7
# the default cube is 2 units wide, add another .2 for gap
spacing = 2.2

for x in range(grid_size):
    for y in range(grid_size):
        f = (x*grid_size) + y
        bpy.ops.mesh.primitive_cube_add (location=(x*spacing, y*spacing, 0))
        obj = bpy.context.active_object
        # key as visible on the current frame
        obj.keyframe_insert('hide',frame=f)
        obj.keyframe_insert('hide_render',frame=f)
        # hide it
        obj.hide = True
        obj.hide_render = True
        # key as hidden on the previous frame
        obj.keyframe_insert('hide',frame=f-1)
        obj.keyframe_insert('hide_render',frame=f-1)
        # key as hidden on the next frame
        obj.keyframe_insert('hide',frame=f+1)
        obj.keyframe_insert('hide_render',frame=f+1)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.