# Is it possible to render only keyframes from Dope-sheet?

I want to render only keyframes from Dope-sheet without the in-between frames. Effectively creating hold of last keyframe till there is new keyframe by re-saving (not re-rendering) the previously rendered keyframe as next frame till I hit next keyframe.

I am Aware of OpenGL render for quick test. But I am talking of final test.

I want to do this so that I can block out poses without interpolation. With Final render result it will help check lighting, materials and mesh deformations.

Is this possible?

• I try to understand it - you want to have a tool that: render an animation (image sequence) of all selected objects in opengl, but only on keyframe positions in the timeline? Missing images will replaced by stills? – p2or Jan 12 '15 at 7:06
• Yes you are right except the openGL render. I wish to render with cycles or blender render engine. – ashwin Jan 12 '15 at 7:16

You can use Loom to render all selected keys in the Dope Sheet or Graph Editor either directly within Blender's UI (as usual) or in a new terminal instance (background process) in case you want to continue working while rendering.

Once installed and enabled, you can call the dialog via spacebar by typing Render Selected Keyfr..., which automatically adds the frames of all selected keys to the render list.

Click to enlarge

As part of the Encoding Dialog (CtrlShiftF9), the addon also provides an utility function to fill the gaps of the image sequence with copies of the nearest frame to get the full lenght animation:

### Minimal Example

Following script is a minimal example for demonstration purposes. The operator allows to render the frames of all selected keys in the Dope Sheet and Graph Editor, all keys that are associated with objects currently selected in the viewport or just all keys of the blend file. Enable Frame Duplication to fill in the missing frames of the sequence with copies of existing frames, after the rendering is done.

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import bpy
from math import ceil
from os import path

bl_info = {
"name": "Render Animatic",
"author": "p2or",
"version": (0, 1),
"blender": (2, 77, 0),
"location": "Spacebar > Render Animatic",
"category": "Render"
}

class RENDER_OT_render_animatic(bpy.types.Operator):
"""Render Animatic"""
bl_idname = "render.render_animatic"
bl_label = "Render Animatic"
bl_options = {'REGISTER'}

render_action = bpy.props.EnumProperty(
name="Render Action",
description="Render Action",
items=(
("SEL", "Selected Keyframes",
"Renders all selected Keys in the Dope Sheet or Graph Editor"),
("OBJ", "Keyframes of Selected Objects",
"Renders all keyframes assigned to any selected object"),
("ALL", "All Keyframes",
"Renders all Keyframes of the Scene"))
)

image_duplication = bpy.props.BoolProperty(
name="Frame Duplication",
default=False,
description="Fills the sequence with copies of "\
"existing (previously rendered) frames"
)

in_out_points = bpy.props.BoolProperty(
name="In and Out Points",
default=False,
description="Add Start and End Frame of the Timeline "\
"to the Render List"
)

open_gl = bpy.props.BoolProperty(
name="OpenGL Rendering",
default=False,
description="Force OpenGL Rendering"
)

def keys_objs(self, obj_list):
"""
Returns the keyframes of all
objects in the given list
"""
ctrl_points = set()
for obj in obj_list:
anim = obj.animation_data
if anim is not None and anim.action is not None:
for channel in anim.action.fcurves:
for key in channel.keyframe_points:
return ctrl_points

def keys_all(self):
"""
Returns the keyframes of all
available control points
"""
ctrl_points = set()
for action in bpy.data.actions:
for channel in action.fcurves:
for key in channel.keyframe_points:
return ctrl_points

def keys_selected(self):
"""
Returns the frames of all
selected control points
"""
ctrl_points = set()
for action in bpy.data.actions:
for channel in action.fcurves:
for key in channel.keyframe_points:
if key.select_control_point:
return ctrl_points

@classmethod
def poll(cls, context):
return bool(bpy.data.actions)

def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=600)

def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "render_action", expand=True)
layout.separator()
col = layout.column(align=True)
row = col.row(align=True)
row.prop(self, "open_gl", icon='MESH_ICOSPHERE', toggle=True)
row.prop(self, "in_out_points", icon='OUTLINER_DATA_ARMATURE', toggle=True)
col = col.column(align=True)
col.prop(self, "image_duplication", icon='GHOST', toggle=True)
layout.separator()

def execute(self, context):
scn = context.scene
output_path = scn.render.filepath
keys = None

if not scn.render.use_overwrite:
error = "Nothing to do, 'use_overwrite' property disabled"
self.report({'ERROR'}, error)
return {'CANCELLED'}

# Get the keyframes
if self.render_action == "ALL":
keys = self.keys_all()
elif self.render_action == "OBJ":
keys = self.keys_objs(context.selected_objects)
elif self.render_action == "SEL":
keys = self.keys_selected()
if not keys:
self.report({'ERROR'}, "No keyframes, operation cancelled")
return {'CANCELLED'}

if self.in_out_points:
keys.update((scn.frame_start,scn.frame_end))

frame_numbers = sorted(list(keys))
rendered_frames = []
frames_to_copy = {}

previous_frame = frame_numbers[0]
for fn in range(frame_numbers[0], frame_numbers[-1]+1):
path_frame = scn.render.frame_path(frame=fn)
if fn in frame_numbers:
scn.frame_set(fn)
scn.render.filepath = path_frame
if self.open_gl and scn.render.use_overwrite:
bpy.ops.render.opengl(write_still=True)
elif not self.open_gl and scn.render.use_overwrite:
bpy.ops.render.render(write_still=True)
previous_frame = path_frame
scn.render.filepath = output_path
rendered_frames.append(fn)
elif self.image_duplication and fn not in frame_numbers:
frames_to_copy.setdefault(previous_frame, []).append(path_frame)

scn.render.filepath = output_path
if rendered_frames:
folder = path.dirname(scn.render.frame_path(frame=0))
info = "Rendered: {}".format(", ".join(map(str, rendered_frames)))
self.report({'INFO'}, info)

# Image duplication
if self.image_duplication:
if len(frames_to_copy.keys()) < 1:
self.report({'ERROR'}, "Only 1 Frame, nothing to copy")
else:
for src, dest in frames_to_copy.items():
from shutil import copyfile
try:
for ff in dest:
copyfile(src, ff)
except OSError:
error = "Error while trying to copy one of the frames"
self.report({'ERROR'}, error)
return {'CANCELLED'}

return {'FINISHED'}

def register():
bpy.utils.register_class(RENDER_OT_render_animatic)

def unregister():
bpy.utils.unregister_class(RENDER_OT_render_animatic)

if __name__ == "__main__":
register()

# test call
bpy.ops.render.render_animatic('INVOKE_DEFAULT')


Note: Unfortunately, the execution blocks the interface while rendering and there is no easy way around that (at the moment). If you are interested on how to display the render progress within the Image Editor as usual, have a look into the source of loom as one of the options. However, command line rendering is probably the better approach anyway, it takes less effort to implement and this way the memory consumption while rendering is lower.

Related

# Render all frames of the selected object(s) that have a keyframe:

import os
import bpy

original_frame = bpy.context.scene.frame_current
output_path = bpy.context.scene.render.filepath
start = bpy.context.scene.frame_start
end = bpy.context.scene.frame_end
frames = []

for obj in bpy.context.selected_objects:
for fcurve in obj.animation_data.action.fcurves:
for keyframe_point in fcurve.keyframe_points:
x, y = keyframe_point.co
if x >= start and x <= end and x not in frames:
frames.append(x)

for frame in frames:
bpy.context.scene.frame_current = frame
bpy.context.scene.render.filepath = os.path.join(output_path, "%05d" % frame)
bpy.ops.render.render(write_still=True)

bpy.context.scene.frame_current = original_frame
bpy.context.scene.render.filepath = output_path

• if you find time [have look at this](blenderartists.org/forum/… Animatic preview is what i am trying achieve finally. So we can either keep saving the old frame with incremented file name till next key frame or play it with interval. But you solution is helping me in big way already thanks – ashwin Jul 9 '13 at 17:13
• @ashwin, I don't understand the desired work-flow. For an animatic I'd use constant interpolation like CharlesL suggested and just render a OpenGL animation. – Aldrik Jul 9 '13 at 18:57
• Yah, but every time it render inbetween frame though there is no change happening, becoz of constant interpolation. So instead of rendering it again we could just buffer the frame and save it with incrementing number till we hit next key frame. saving render time. you are right i use OpenGL render its good. But Final render animatic is worth some times. And more often for client presentation. But your solution already helps to 70% (thats a lot of time saving) so all i need is to place these frames with right interval and render video with audio its ready :) – ashwin Jul 10 '13 at 5:20
• frame_set() should be called to change the frame. The supplied time is automatically casted to integer (like ceil()), so no rounding required. If you wanted to render all sub-frames, you could do frame_set(frame, frame % 1). – CoDEmanX Jan 8 '15 at 17:50
• While I'm sure this works, it's not a very helpful answer. It'd be great if you could explain the code in plain English so that someone might learn from it, rather than simply copy-paste it. At the very least there should be some comments in the code to explain it. – Greg Zaal Jan 9 '15 at 12:22

You can use the constant offset interpolation. To do so:

1. Go to the Graph Editor and select all your curves (A)
2. Press T and select constant.

Your animation will now consist of poses without any interpolation in between. Keep in mind that your pose will not change at the middle of a changed pose. It will change at the end.

• I am aware of this but while rendering it still renders inbetween frames. Intern not saving time. Thanks for your reply – ashwin Jul 9 '13 at 9:50