1
$\begingroup$

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!

$\endgroup$
4
  • $\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, 2018 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, 2018 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, 2018 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, 2018 at 22:20

1 Answer 1

3
$\begingroup$

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:
            print(cam.name)
            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')
            else:
                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
        print(output)
        return output


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


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


if __name__ == "__main__":
    register()
$\endgroup$
6
  • $\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, 2018 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, 2018 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, 2018 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, 2018 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, 2018 at 15:21

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .