12
$\begingroup$

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?

$\endgroup$
  • $\begingroup$ 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? $\endgroup$ – p2or Jan 12 '15 at 7:06
  • $\begingroup$ Yes you are right except the openGL render. I wish to render with cycles or blender render engine. $\endgroup$ – ashwin Jan 12 '15 at 7:16
4
+50
$\begingroup$

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.

enter image description here 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:

enter image description here


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.

enter image description here

# ##### 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

$\endgroup$
8
$\begingroup$

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
$\endgroup$
  • $\begingroup$ 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 $\endgroup$ – ashwin Jul 9 '13 at 17:13
  • $\begingroup$ @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. $\endgroup$ – Aldrik Jul 9 '13 at 18:57
  • $\begingroup$ 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 :) $\endgroup$ – ashwin Jul 10 '13 at 5:20
  • 1
    $\begingroup$ 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). $\endgroup$ – CoDEmanX Jan 8 '15 at 17:50
  • 5
    $\begingroup$ 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. $\endgroup$ – Greg Zaal Jan 9 '15 at 12:22
4
$\begingroup$

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.

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

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.