Loom Addon
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
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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:
ctrl_points.add((ceil(key.co.x)))
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:
ctrl_points.add(ceil(key.co.x))
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:
ctrl_points.add(ceil(key.co.x))
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 = {}
# Render business
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