Halloween Costume ideas 2015

PYTHON / Random Keyframe Animation



The next thing I am curious about is on how Blender Python does keyframing for animation.

We already know how to LIST, how to MODIFY VALUES, now we see how we can KEYFRAME VALUES using Python.

SIMPLE LOCATION KEYFRAME
Reference:
http://www.blender.org/forum/viewtopic.php?t=22602&sid=546436de57ad2f620b88ef9d8d8cfddb

From the forum post above, we have one cool code that we can observe. The code presented seems to work on currently selected object, it basically made 1000 keyframes (for Location) of currently selected object: These 2 BPY below does the keyframing job.


bpy.context.scene.frame_current = i 
bpy.ops.anim.keyframe_insert(type='Location')


I modified the script slightly, by changing the object positions using Random values.

USING WHILE LOOP FOR KEYFRAMING RANDOM POSITION

If we use While Loop and Random Positions:


import bpy
import random

i = 0

while(i <= 100): 
    bpy.context.scene.frame_current = i 
    bpy.ops.anim.keyframe_insert(type='Location')
    
    movx = random.uniform(-5,5)
    movy = random.uniform(-5,5)
    movz = random.uniform(-5,5)
     
    bpy.data.objects['Cube'].location = (movx, movy, movz)
    i += 1


Using While Loop like above for keyframing animation, we basically tell Blender to step one frame and create keyframes. The result is a dense keyframes like Motion Capture data.

Of course we can change that behaviour of keyframe stepping by changing that i += 1 expression at the bottom to a different value.


import bpy
import random

i = 0

while(i <= 100): 
    bpy.context.scene.frame_current = i 
    bpy.ops.anim.keyframe_insert(type='Location')
    
    movx = random.uniform(-5,5)
    movy = random.uniform(-5,5)
    movz = random.uniform(-5,5)
     
    bpy.data.objects['Cube'].location = (movx, movy, movz)
    i += 20



USING FOR LOOP FOR KEYFRAMING  RANDOM POSITION
If we use For Loop, range() function we can have different control on where Blender should create a keyframe. For this example code below, it is basically telling Blender to set keyframe every 5th of the frame between range of frames 0 - 100.


import bpy
import random

for i in range(0,100,5): 
    bpy.context.scene.frame_current = i 
    bpy.ops.anim.keyframe_insert(type='Location')
    
    movx = random.uniform(-5,5)
    movy = random.uniform(-5,5)
    movz = random.uniform(-5,5)
     
    bpy.data.objects['Cube'].location = (movx, movy, movz)



Next example below is the same code as above, but works on multiple objects (that are currently selected):



import bpy
import random

objects = bpy.context.selected_objects

for i in range(0,100,5):     
    for object in objects:    
        bpy.context.scene.frame_current = i 
        bpy.ops.anim.keyframe_insert(type='Location')
        
        movx = random.uniform(-5,5)
        movy = random.uniform(-5,5)
        movz = random.uniform(-5,5)
         
        object.location = (movx, movy, movz)




Nothing really special here, other than the fact we can tell Blender to move forward in timeline and set keyframe. Super simple, but you can already do quite a lot with this.

It is a good start of procedural creating animation in Python context.

ANOTHER WAY TO SET KEYFRAME
I just do a quick experiment, apparently you can also do keyframing by doing this:

bpy.data.objects[NAME].keyframe_insert(DATAPATH, frame=FRAMENUMBER)

For example:
bpy.data.objects['Cube'].keyframe_insert('location', frame=15)
>> Set kefyrame for object named Cube, for it's 'location' datapath, at frame 15.

Doing this seems to be more specific and directly targeting the object.

Let's give it a try.

To find DATA PATH, I usually check from the Channel or Property, by RMB (right mouse button) click and Copy Data Path:


Typical Data Path, as below:

  • 'location'
  • 'rotation_euler'
  • 'scale'
But Data Path can be anything, including custom attributes, could be the Material and its attributes.

RANDOM SCALE
Below example will randomize the Scale and set key every 5 frames and I am using this different method.

import bpy
import random

objects = bpy.context.selected_objects

for i in range(0,100,5):     
    for object in objects:    
        bpy.context.scene.frame_current = i 
        #print(i)
        #bpy.ops.anim.keyframe_insert(type='Location')
        
        scalex = random.uniform(1.0,4.0)
        scaley = random.uniform(1.0,4.0)
        scalez = random.uniform(1.0,4.0)
        
        object.scale = scalex, scaley, scalez
        
        object.keyframe_insert('scale')


Using the same pattern above, we can actually be specific on when we like to set keyframe. Let say, we want to specifically keyframe the Scale at frames: 50, 100, 150, 200, we simply change the loop like below:


import bpy
import random

objects = bpy.context.selected_objects

for i in [50,100,150,200]:     
    for object in objects:    
        bpy.context.scene.frame_current = i 
        #print(i)
        #bpy.ops.anim.keyframe_insert(type='Location')
        
        scalex = random.uniform(1.0,4.0)
        scaley = random.uniform(1.0,4.0)
        scalez = random.uniform(1.0,4.0)
        
        object.scale = scalex, scaley, scalez
        
        object.keyframe_insert('scale')



KEYFRAMING MATERIAL PROPERTY

Let's try applying random keyframes on Material 'diffuse_color' of selected object.

Now, with things like these, we need to be careful and ask ourselves:

  • Does our object already have material? Should we create one using Python if the object does not have material?
  • Do we want to just give random Diffuse Color to all Material in the scene? Maybe not. We want to specifically target the Material currently applied to our selected object.
RANDOM OBJECT COLOR
Before going to Material, we can actually do Object Color for simplicity. Any object in the scene will have Object Color attribute by default that we can color.

import bpy
import random

objects = bpy.context.selected_objects

for i in range(0,100,5):     
    for object in objects:    
        bpy.context.scene.frame_current = i 
        #print(i)
        #bpy.ops.anim.keyframe_insert(type='Location')
        
        colorR = random.uniform(0.0,1.0)
        colorG = random.uniform(0.0,1.0)
        colorB = random.uniform(0.0,1.0)
        alpha = 1.0
        
        object.color = colorR, colorG, colorB, alpha
        
        object.keyframe_insert('color')

They are animating and changing color.

Object Color is interesting and so easy to access, maybe we can use it to do Animated Pixel Art?


RANDOM MATERIAL DIFFUSE COLOR
bpy.data.objects['Cube'].data.materials[NAME].keyframe_insert('diffuse_color')

The above Blender Python will insert a keyframe at current frame in the timeline. As you can see, with this method you are very specific with everything.

Let's try a code that does the keyframing of this Material Diffuse Color, of currently selected object. Usually with my code, I always start by making a Loop over every object, in selected objects, and every materials that are applied to this object. If we need to skip on anything, the code can then modified further.


import bpy
import random

objects = bpy.context.selected_objects

for i in range(0,100,5):     
    for object in objects:
        # Get all materials of currently selected object (if any)
        myMaterials = object.data.materials

        for material in myMaterials:
                    
            bpy.context.scene.frame_current = i 
            
            colorR = random.uniform(0.0,1.0)
            colorG = random.uniform(0.0,1.0)
            colorB = random.uniform(0.0,1.0)

            
            material.diffuse_color = colorR, colorG, colorB    
            material.keyframe_insert('diffuse_color')




Maybe we can also target certain Object and Material by its name? Yes, I think we can do that using the FILTERING code that I have mentioned previously.

CONCLUSION
From above examples, we have learned how to do keyframing for common attributes, which values we also randomized overtime.

Adding "animation" really makes the difference to previously static randomness.

Of course, it is still pretty boring and linear, but you can play around with the timing of the keyframe for each loops by offsetting the FRAME slightly. You can get like Airport Flicker Board effect.

But even for such simple animation like Scale value, from A to B, you can get pretty interesting result by offsetting the animation of an array of objects.

For example, if we like to OFFSET keyframe for every selected object:

import bpy

selected_objects = bpy.context.selected_objects

bpy.context.scene.frame_current = 1

for object in selected_objects:
    bpy.context.scene.frame_current += 3
    object.scale = 1.01.0, 1.0
    object.keyframe_insert('scale')


This is a kind of thing that you cannot easily achieve with manual keyframing. Easy to add complexity. But it is always better to plan things.

There is one more thing that I have not tried:
To be able to Offset via Blender Action. Probably something to check out next. Especially in relation to Armature to generate some kind of crowd simulations.

To be honest, I like to do this kind of thing in more procedural way using nodes. But I guess for now, we can try setting it up using Python and maybe Dynamic Paint and Drivers.

The procedure we applied so far is always the same:
1. MAKE CHANGES: Randomize the value of attributes of array of objects.
2. SET KEYFRAMES: Keyframes those particular attributes at particular frame in time --> this is the NEW thing we learned today.

APPLICATION: Pixel Art Animation

Alrightly, we can now revisit the Pixel Art script and modify it slightly for our purpose.

Instead of using Vertex Color, we will simplify this further to use Object Color. We will also keyframe the Object Color.

The process will be like this:
- Initial Setup, the import of the grid and colorize Object Color using Pixel Art image.
- Keyframe the Object Color at certain frame
- Import another Pixel Art image and Modify the current setup
- Keyframe the Object Color at certain frame

The scripts will be something like below:

1. Import a Pixel Art, setup initial Grid Matrix of Pixel Cube.

import bpy

D = bpy.data

# Specify your image here, open it inside Blender Image Editor panel
image_file = 'ryu_16_16.png'
img = D.images[image_file]
pixels = list(img.pixels)
grouped_list = [pixels[ipx:ipx+4] for ipx in range(0, len(pixels), 4)]

# Get width and height of image (in pixel)
w = width = img.size[0]
h = height = img.size[1]
print("------LENGTH------")
print( len(img.pixels)//4)
print("------PIXEL------")
print(pixels)
print("---GROUPED-PIXEL---")
print(len(grouped_list))

# Create LIST of GRID XY value
rowColList=[]
for i in range(height):
    for j in range(width):
      rowColList.append(i)
      rowColList.append(j)

# SPLIT the LIST of XY
origList = rowColList
splitList = [origList[i:i+2] for i in range(0,len(origList),2)]

# Create a single material that respect Object Color
mat = bpy.data.materials.new('PixelObjectColorMat')
mat.use_object_color = True

for number in range(len(grouped_list)):
    # Separate RGBA into each own variables
    r = grouped_list[number][0]
    g = grouped_list[number][1]
    b = grouped_list[number][2]
    a = grouped_list[number][3]
    
    # Separate XY coordinate
    x=splitList[number][0]
    y=splitList[number][1]
    
    # Create cube at location XY
    bpy.ops.mesh.primitive_cube_add(location=(x-width/2, y-width/2, 0))
    
    selectedObject = bpy.context.selected_objects
    mesh = selectedObject[0]
    mesh.name = 'pixelcube.%s' % number
    mesh.scale = 0.45, 0.45, 0.45
    
    # Keep list of our mesh for further processing, this is one big array
    #pixelCubeObjects.append(mesh)
    # bpy.data.objects[pixel'cube.1022']
    
    # Apply our special Material
    bpy.context.object.data.materials.append(mat)
    
    # Assign RGBA color to Object Color
    mesh.color = r,g,b,a
    
    # Print the chatter progress
    print ('PixelCube {number} created at:'.format(number=number), x, y, ', with RGB color', r, g, b)



2. Set keyframes on each Pixel Cube Object Color.


import bpy

D = bpy.data

# Specify your image here, open it inside Blender Image Editor panel
image_file = 'ryu_16_16.png'
img = D.images[image_file]
pixels = list(img.pixels)
grouped_list = [pixels[ipx:ipx+4] for ipx in range(0, len(pixels), 4)]

# Get width and height of image (in pixel)
w = width = img.size[0]
h = height = img.size[1]

current_frame = 1 # # SET YOUR KEYFRAME HERE

for number in range(len( grouped_list )):
    bpy.data.objects['pixelcube.{number}'.format(number=number)].keyframe_insert('color', frame = current_frame)


3. Modify the color of each Pixel Cube Object Color with the new Pixel Art.

import bpy

D = bpy.data

# Specify your image here, open it inside Blender Image Editor panel
image_file = 'ken_16_16.png'
img = D.images[image_file]
pixels = list(img.pixels)
grouped_list = [pixels[ipx:ipx+4] for ipx in range(0, len(pixels), 4)]


# Get width and height of image (in pixel)
w = width = img.size[0]
h = height = img.size[1]
print("------LENGTH------")
print( len(img.pixels)//4)
print("------PIXEL------")
print(pixels)
print("---GROUPED-PIXEL---")
print(len(grouped_list))

for number in range(len( grouped_list )):
    
    r = grouped_list[number][0]
    g = grouped_list[number][1]
    b = grouped_list[number][2]
    a = grouped_list[number][3]
     
    print('-'*50)
    print(number)
    print(r,g,b,a)
    
    bpy.data.objects['pixelcube.{number}'.format(number=number)].color = r,g,b,a
     
4. Set keyframes AGAIN on each Pixel Cube Object Color (now with new Pixel Art)

import bpy

D = bpy.data

# Specify your image here, open it inside Blender Image Editor panel
image_file = 'ken_16_16.png'
img = D.images[image_file]
pixels = list(img.pixels)
grouped_list = [pixels[ipx:ipx+4] for ipx in range(0, len(pixels), 4)]

# Get width and height of image (in pixel)
w = width = img.size[0]
h = height = img.size[1]

current_frame = 60 # SET YOUR KEYFRAME HERE

for number in range(len( grouped_list )):
    bpy.data.objects['pixelcube.{number}'.format(number=number)].keyframe_insert('color', frame = current_frame)

.... and so on.

SOMETHING TO THINK ABOUT
Now that I look at the code above, I started to think that I need to apply "Top Down Design".

The code above works okey, but in order to make it more useful and easier to use for user, I probably need to tidy them up.

Before even thinking for creating UI, I probably need to simplify the code:
1. IMPORT: Import Pixel Art Image, limit to 64x64? Any bigger will be user's risk.
2. CREATE: Create 3D Cube array matrix based on Pixel Art.
3. MODIFY: If we like to modify, we need to specify the exact same Pixel Art dimension. This is easy to do as long we can access the same array of 3D Cube.
4. KEYFRAME: Keyframe the Object Color at certain frame.
5. VARIATION: Things like Offset is something to think about for best implementation.


Post a Comment

MKRdezign

Contact Form

Name

Email *

Message *

Powered by Blogger.
Javascript DisablePlease Enable Javascript To See All Widget