Take a Spin: How to Create Blender Python Camera Rotation

In this post, we show how to create a UI panel and Python script to rotate a camera (or other object) around a target. This builds on our previous post where we created a camera rotation script.

The Code

Here is the full code. Note that throughout the script, we call the rotating object a camera. However, this could be any object.

#import the modules
import bpy
import numpy as np
import math
from mathutils import *


# Property Group for the settings
class RotationPropertyGroup(bpy.types.PropertyGroup):
   speed: bpy.props.FloatProperty(name="speed",default=1, description="number of rotations in timeframe")
   numKeyframes: bpy.props.IntProperty(name="keyframes",default=30, description="number of keyframes to create")
   target: bpy.props.StringProperty(name="target", description="target object to rotate around")
   
class RotationOperator(bpy.types.Operator):
   """Adds keyframes to an object to rotate around another object."""
   bl_idname = "object.rotation_operator"
   bl_label = "Camera Rotation"


   def rotateCamera(self,frameNumber):
       """Rotate the camera around an object."""
      
       newTheta = self.theta*frameNumber
       rotationMatrix = np.array([[math.cos(newTheta),-math.sin(newTheta),0],
                               [math.sin(newTheta), math.cos(newTheta),0],
                               [0,0,1]])
       return np.dot(self.cameraOrigin,rotationMatrix) + self.focusObject.location   


   def execute(self, context):
       # Create a variable to hold the currently selected object
       camera = bpy.context.object
       self.cameraOrigin = Vector(camera.location)


       # Create a variable to hold the focus object
       self.focusObject = bpy.data.objects[bpy.context.object.rotation_prop.target]
      
       keyframes = bpy.context.object.rotation_prop.numKeyframes
       speed = bpy.context.object.rotation_prop.speed


       # Angle (in radians) that the camera will rotate each frame
       self.theta = 2*math.pi*speed/(bpy.context.scene.frame_end - bpy.context.scene.frame_start)
       framesPerKey = (bpy.context.scene.frame_end - bpy.context.scene.frame_start)/keyframes
      
       for currentKeyframe in range(0,keyframes):
           currentFrame = currentKeyframe*framesPerKey + 1
           newPos = self.rotateCamera(currentFrame)
           camera.location = newPos
           camera.keyframe_insert(data_path='location',frame=currentFrame+bpy.context.scene.frame_start-1)
          
       # Reset to the first frame
       bpy.context.scene.frame_current = bpy.context.scene.frame_start
          
       return {'FINISHED'}


class RotationClearOperator(bpy.types.Operator):
   """Clear keyframes."""
   bl_idname = "object.rotation_clear_operator"
   bl_label = "Clear object rotation keyframes"
  
   def execute(self, context):
       return bpy.ops.anim.keyframe_clear_v3d()
   
class RotationPanel(bpy.types.Panel):
   bl_idname = 'VIEW3D_PT_rotation_panel'
   bl_label = 'Camera Rotation'
   bl_space_type = 'VIEW_3D'
   bl_region_type = 'UI'
  
   def draw(self, context):  
       self.layout.prop(bpy.context.object.rotation_prop,"speed")
       self.layout.prop(bpy.context.object.rotation_prop,"numKeyframes")
       self.layout.prop_search(bpy.context.object.rotation_prop, "target", bpy.context.scene, "objects")
       self.layout.separator()
       self.layout.operator(RotationOperator.bl_idname, text=RotationOperator.bl_label)
       self.layout.operator(RotationClearOperator.bl_idname,text="Clear keyframes")
      
def register():
   bpy.utils.register_class(RotationPropertyGroup)
   bpy.utils.register_class(RotationOperator)
   bpy.utils.register_class(RotationClearOperator)
   bpy.utils.register_class(RotationPanel)
  
   # Create pointer reference for the property group
   bpy.types.Object.rotation_prop = bpy.props.PointerProperty(type=RotationPropertyGroup)
  
if __name__ == "__main__":
   register()

Rotation Property Group

To start, we define a rotation property group. A property group is a convenient way of keeping related properties together. In this case, we have three properties:

  • speed: determines the speed of the rotation
  • numKeyframes: the number of keyframes to create. More keyframes results in a smoother rotation
  • target: target object to rotate around
# Property Group for the settings
class RotationPropertyGroup(bpy.types.PropertyGroup):
   speed: bpy.props.FloatProperty(name="speed",default=1, description="number of rotations in timeframe")
   numKeyframes: bpy.props.IntProperty(name="keyframes",default=30, description="number of keyframes to create")
   target: bpy.props.StringProperty(name="target", description="target object to rotate around")

Camera Rotation Operator

Next, we create a rotation operator. This operator will be called when the user clicks a button to run the rotation. The operator will cycle through the target frames and add keyframes at set intervals.

Camera Rotation Method

We create a rotateCamera method. This function takes the current frame number as a parameter. It then calculates the current location of the camera using a rotation matrix (and Numpy). For more detail on the math, see here.

def rotateCamera(self,frameNumber):
       """Rotate the camera around an object."""
      
       newTheta = self.theta*frameNumber
       rotationMatrix = np.array([[math.cos(newTheta),-math.sin(newTheta),0],
                               [math.sin(newTheta), math.cos(newTheta),0],
                               [0,0,1]])
       return np.dot(self.cameraOrigin,rotationMatrix) + self.focusObject.location 

Execute the Camera Rotation Operator

The next method executes the operator.

The Variables

We create variables to hold the currently selected object and the start location of the currently selected object.

camera = bpy.context.object
self.cameraOrigin = Vector(camera.location)

Next, we create variables to hold the user specified information from the UI panel (more on the UI panel later). Specifically, the target object, the number of keyframes to create, and the speed of the rotation.

self.focusObject = bpy.data.objects[bpy.context.object.rotation_prop.target]
keyframes = bpy.context.object.rotation_prop.numKeyframes
speed = bpy.context.object.rotation_prop.speed

We then calculate the rotation angle per frame (in radians) and the number of frames per keyframe:

self.theta = 2*math.pi*speed/(bpy.context.scene.frame_end - bpy.context.scene.frame_start)
framesPerKey = (bpy.context.scene.frame_end - bpy.context.scene.frame_start)/keyframes

The Camera Rotation Loop

Now we create the keyframes. We loop through the number of keyframes. For each iteration of the loop, we call the rotateCamera method, which returns the next position. We then insert a keyframe. We adjust the insertion keyframe based on the scene.frame_start (in case the user doesn’t want the rotation to start on the first frame).

for currentKeyframe in range(0,keyframes):
    currentFrame = currentKeyframe*framesPerKey + 1
    newPos = self.rotateCamera(currentFrame)
    camera.location = newPos
    camera.keyframe_insert(data_path='location', frame=currentFrame+bpy.context.scene.frame_start-1)

Finally, we change the current frame back to the start frame.

bpy.context.scene.frame_current = bpy.context.scene.frame_start
Example of camera rotation around a cube

Clearing the Camera Rotation

Since the rotation operator works by adding keyframes, we want an easy way to clean up the keyframes. Therefore, we also create an operator that clears the keyframes on the current object. This requires a simple call to the existing operator

bpy.ops.anim.keyframe_clear_v3d()

The UI Panel

We have our operators, now we need to create the interface.

class RotationPanel(bpy.types.Panel):
   bl_idname = 'VIEW3D_PT_rotation_panel'
   bl_label = 'Camera Rotation'
   bl_space_type = 'VIEW_3D'
   bl_region_type = 'UI'
  
   def draw(self, context):  
       self.layout.prop(bpy.context.object.rotation_prop,"speed")
       self.layout.prop(bpy.context.object.rotation_prop,"numKeyframes")
       self.layout.prop_search(bpy.context.object.rotation_prop, "target", bpy.context.scene, "objects")
       self.layout.separator()
       self.layout.operator(RotationOperator.bl_idname, text=RotationOperator.bl_label)
       self.layout.operator(RotationClearOperator.bl_idname,text="Clear keyframes")

Location, Location, Location

The local variables of the class set the name of the panel, but they also determine where the panel will appear. Specifically, bl_space_type is set to VIEW_3D, which tells Blender to place this in the 3D viewport.

bl_idname = 'VIEW3D_PT_rotation_panel'
bl_label = 'Camera Rotation'
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
Example of camera rotation panel in 3D view.

Draw the UI Elements

Next, we create the draw method. This method has two parts, the first 3 lines show the properties that we want to allow the user to edit. Blender takes care of determining what type of UI element to show based on the type of property. For example, speed and keyframes are just numbers, while the prop_search creates a dropdown of current objects.

self.layout.prop(bpy.context.object.rotation_prop,"speed")
self.layout.prop(bpy.context.object.rotation_prop,"numKeyframes")
self.layout.prop_search(bpy.context.object.rotation_prop, "target", bpy.context.scene, "objects")

We add a separator for spacing:

self.layout.separator()

Then we add two buttons. One creates the key frames, and the other deletes them.

self.layout.operator(RotationOperator.bl_idname, text=RotationOperator.bl_label)
self.layout.operator(RotationClearOperator.bl_idname,text="Clear keyframes")

Register the Classes

We register the four subclasses that we created:

  • RotationPropertyGroup
  • RotationOperator
  • RotationClearOperator
  • RotationPanel
bpy.utils.register_class(RotationPropertyGroup)
bpy.utils.register_class(RotationOperator)
bpy.utils.register_class(RotationClearOperator)
bpy.utils.register_class(RotationPanel)

Next, we create a pointer property to our RotationPropertyGroup and assign it to the objects in the scene. This allows each object in the scene to have its own speed and number of keyframes. We could assign this everywhere, because these are only used to make the keyframes for the object. However, I think assigning this to the object makes sense so we can remember the setting that we used for this particular object if we have multiple rotating objects in the scene.

# Create pointer reference for the property group
bpy.types.Object.rotation_prop = bpy.props.PointerProperty(type=RotationPropertyGroup)

Conclusion

I am excited about this UI panel. I like to add camera rotation to my animations. This UI panel allows me to easily add the rotation and to customize the look of the rotation. The last thing that I do is set the track to constraint to the object I am rotating. However, depending on what you are rotating, you may not want that (e.g., a planet rotating around a sun). Please let me know in the comments if there are any other features that would be useful in this panel.