I'm currently working on a project in 2.79b containing multiple shots that require several cameras (2+ keyframes for Location with a Linear Extrapolation applied to them) to be moving at the same speed.

At the moment, my workflow involves placing 2 keyframes for the active camera's Location and essentially estimating how far apart they should be spaced in order for the speeds to match.

I am looking to create a Python Script that logs the speed of cameras in the scene (in Blender Units per second (U/s)?), on each frame, and outputs it to the console, for example. I'd also like it to output this value every time a keyframe on the current camera is changed, preferably in real-time.

Here's a (very rough!) example of what I'm hoping to achieve in terms of output...

Camera Speed Example 01

What would be the most practical method of scripting this? Would it be best to find the distance travelled by the camera, for example, between the current and previous frame (e.g. through the difference in location), and then perform a "Speed = Distance/Time" calculation to obtain the speed, or is there a function in the Python API that provides access to this data automatically?

Any help would be much appreciated, thanks!

  • $\begingroup$ why not simply parent both cameras to empties, and re-use the same animation action for both? This way, you could space the cameras apart from each other by moving the empties, and only animate one camera, while the other one follows automatically. $\endgroup$ – aliasguru Nov 10 '18 at 19:48
  • $\begingroup$ Thanks, @aliasguru , I was probably a bit unclear, but whilst I need the speed of the camera's movement to be the same, the direction will usually be different (pan up/down/left/right, zoom in/out, etc). The project is also split into different files with differing start/end frames, so parenting empties is also problematic in that regard. $\endgroup$ – Hexbob6 Nov 11 '18 at 1:56
  • $\begingroup$ hm I see. Different directions of traversal would still be possible with the Empty method, as the Empty kind of provides a local coordinate system that the camera will operate in. Time offsets would be possible using NLA. But anyways, both is not really answering the question itself. $\endgroup$ – aliasguru Nov 11 '18 at 9:47
  • $\begingroup$ Interesting, I'll admit I'm not too familiar with the NLA, but I think I'd preferably go down the python route for this one as a first resort - I'll update my question to be more specific. Thanks anyway, @aliasguru ! $\endgroup$ – Hexbob6 Nov 14 '18 at 22:20

On every sceneupdate we can call a function which checks for fcurves and then evaluate the current and previous frame.

The script creates a panel in the camera data section and displays the speed there.

panel demo

import bpy

class CameraSpeedPanel(bpy.types.Panel):
    """Creates a Panel in the Data properties window
        when a camera is selected"""
    bl_label = "Camera Speed"
    bl_idname = "CAMERA_PT_speedometer"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "data"

    def draw(self, context):
        layout = self.layout

        cameras = [ob for ob in context.scene.objects if ob.type == 'CAMERA']

        for cam in cameras:
            row = layout.row()
            lbl = cam.name
            if context.scene.camera == cam:
                lbl += ' (active)'
            if cam == context.scene.objects.active:
                row.label(lbl, icon = 'OUTLINER_OB_CAMERA')
                row.label(lbl, icon = 'CAMERA_DATA')
            row.label(self.getSpeed(context.scene, cam))

    def getSpeed(self, scene, ob):
        if (ob.animation_data == None or
            ob.animation_data.action == None):
            return '(not animated)'
        current = ob.location.copy()
        previous = ob.location.copy()
        frame = scene.frame_current
        for i in range(3):
            crv = ob.animation_data.action.fcurves.find('location', i)
            if (crv != None):
                current[i] = crv.evaluate(frame)
                previous[i] = crv.evaluate(frame - 1)
        output = str((current - previous).length)
        output = (output[:10] + '..') if len(output) > 12 else output
        return output

def register():

def unregister():

if __name__ == "__main__":
| improve this answer | |
  • $\begingroup$ Perfect, @Leander , that seems to be almost exactly what I'm looking for, thanks! Presumably I would also be able to set up the script to output to a UI panel, for example, if I didn't want to always have a text object in the scene? Also, if it's not too much additional hassle to ask, would you mind providing some comments - I'm a bit new to Python in general and although I can work out most of the code, there're a couple of things that I'm a bit unsure of? $\endgroup$ – Hexbob6 Nov 15 '18 at 23:03
  • $\begingroup$ @Hex Could you make a list of keywords, which aren't clear. I can add some comments. $\endgroup$ – Leander Nov 16 '18 at 8:09
  • $\begingroup$ Sure thing, it's mainly what the for i in range(3): loop is doing exactly, and the output = (output[:10] + '..') if len(output) > 12 else output) line. And another massive thank you for adding the UI Panel code! $\endgroup$ – Hexbob6 Nov 17 '18 at 14:37
  • $\begingroup$ @Hex Ok, thats relatively easy, although counter intuitive. Where trying to find the X, Y and Z channels of the object location graphs. That's three graphs in Blender. If we were trying to find the corresponding graph to a single animated property, we can just call find without the index parameter. However using find('location') is actually not clear since we can't specify which channel (x/y/z) to use [afaik, this would return a tuple]. [...] $\endgroup$ – Leander Nov 17 '18 at 15:19
  • $\begingroup$ (cont.) Therefore we have to call find('location', index=channel_number)` for each channel. The index [0, 1, 2] corresponds to the graph for the [x, y, z].We have to do this separately, because there is the possibility, that one channel has not been keyframed (and doesn't have a corresponding graph: crv == None), but another one isn't and is using the constant values. Note, that I grab the constant values in the previous lines current = ob.location.copy(). Does this make things clearer? $\endgroup$ – Leander Nov 17 '18 at 15:21

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.