Saturday, February 9, 2019

Blender Playblast Tool

Hi,

This allows playblast of certain frames in blender while filling in the gaps automatically
The goal is to allow animator to work in spline yet make a playblast on two's (Tip from Joan Marc Fuentes)
TODO: put this in blender addon form.
TODO: allow it to use a keyed object to find frames to playblast ...

#Use and Modify at your own risk

#example usage it assumes first frame in list is starting frame:
#naPlayblast( frames = [1,3,5], imageDir = "/Users/Nathaniel/Desktop/SINGLE_BLENDER_FACE_RIG/intro_blender_pipe/output/tmpPlayblast", fill = True)

#this example playblasts on twos
#frames = range( bpy.context.scene.frame_start, bpy.context.scene.frame_end, 2)
#naPlayblast( frames = frames, imageDir = "/Users/Nathaniel/Desktop/SINGLE_BLENDER_FACE_RIG/intro_blender_pipe/output/tmpPlayblast", fill = True)
##

import shutil
import os
import bpy

def naPlayblast( frames = [1,3,5], imageDir = "/Users/Nathaniel/Desktop/SINGLE_BLENDER_FACE_RIG/intro_blender_pipe/output/tmpPlayblast", fill = True):
    if not os.path.exists( imageDir ):
        print('cannot find output directory')
        return
    
    if frames[0] != bpy.context.scene.frame_start:
        print('only works with starting frame given matches with timeline')
        return
    
    setImageExt()
    #playblast chosen frames
    for frame in frames:
        bpy.context.scene.frame_set(frame)
        imageName = getImageNameFromNumber(frame)
        imagePath = os.path.join( imageDir, imageName )
        bpy.data.scenes["Scene"].render.filepath = imagePath
        bpy.ops.render.opengl(write_still=True)
    
    #fill in missing frames
    if fill:
        missingInfoDict = getFillMissingFramesDictionary( frames )
        fillMissingFrames( missingInfoDict, imageDir )

def setImageExt():
    """can change this for different image extension
    """
    bpy.context.scene.render.image_settings.file_format = 'TIFF'
def getImageExt():
    return 'tif'
    
def getImageNameFromNumber( frame = 1 ):
    """can change this to any image name formatting
    """
    return 'img_%d.%s' %(frame, getImageExt() )
    
def getFillMissingFramesDictionary( sourceFrames = [1,3,5] ):
    """keys are source image name values list of destination image names needed to fill
    #this gives info for filling in images
    #ex result given [1,3,5] it assumes always beginning with start frame:  
    #{ 'img_1':['img_2'], 'img_3':[img_4] }
    """
    startFrame = bpy.context.scene.frame_start
    endFrame = bpy.context.scene.frame_end
    if sourceFrames[0] != startFrame:
        print( 'cannot handle source frames not starting at %d.' %startFrame)
        return
        
    result = {}
    for i in range(0,len(sourceFrames)-1):
        #print sourceFrames[i], range( sourceFrames[i]+1, sourceFrames[i+1] )
        sImage = getImageNameFromNumber( sourceFrames[i] )
        dImages = [ getImageNameFromNumber(x) for x in range( sourceFrames[i]+1, sourceFrames[i+1] ) ] 
        result[ sImage ] = dImages
    
    #fill in up to total frames if needed
    if sourceFrames[ len(sourceFrames) - 1 ] != endFrame:
        sImage = getImageNameFromNumber( sourceFrames[len(sourceFrames)-1] )
        dImages = [ getImageNameFromNumber(x) for x in range( sourceFrames[ len(sourceFrames)-1 ]+1, endFrame+1 ) ]     
        result[ sImage ] = dImages
    return result
    

def fillMissingFrames( imgDict = { 'img_1':['img_2'], 'img_3':['img_4'] }, imgDir = '/Users/Nathaniel/Desktop/SINGLE_BLENDER_FACE_RIG/intro_blender_pipe/output/tmpPlayblast' ):
    """
    this does actual copy of images
    #given ex: { 'img_1':['img_2'], 'img_3':[img_4] }, imgDir = '/Users/Nathaniel/Desktop/SINGLE_BLENDER_FACE_RIG/intro_blender_pipe/output/tmpPlayblast' 
    """
    for sourceImage in imgDict.keys():
        destinationImages = imgDict[sourceImage]
        for dImage in destinationImages:
            dImageFull = os.path.join( imgDir, dImage )
            sImageFull = os.path.join( imgDir, sourceImage )
            if not os.path.exists( sImageFull ):
                print('could not find source image %s, skipping' %sImageFull)
                continue
            shutil.copy( sImageFull, dImageFull ) 
 
 
Happy Sketching!
Nate

#inspired by:
#Joan Marc Fuentes  https://vimeo.com/88955189
#https://stackoverflow.com/questions/14982836/rendering-and-saving-images-through-blender-python
#https://blender.stackexchange.com/questions/1101/blender-rendering-automation-build-script
#https://blender.stackexchange.com/questions/27579/render-specific-frames-with-opengl-via-python/27640
#https://stackoverflow.com/questions/27515913/create-a-copy-of-an-image-python
#https://stackoverflow.com/questions/27678156/how-to-count-by-twos-with-pythons-range