Skip to content
Snippets Groups Projects
Commit f4cf9b00 authored by Damien Picard's avatar Damien Picard
Browse files

Add camera rigs: add 2D camera rig

This rig is mostly useful for 2D shots, when the camera is static and
the action happens in front of it (like a theatre stage).

In a 2D production (and some shots in 3D as well), you sometimes need
to rotate the camera while zooming, effectively "cropping" the field,
just as you would using a rostrum camera. This is tedious and
error-prone if animating built-in basic transforms, so this rig
implements a more intuitive way to do that, by just animating the two
lower corners of the camera's field.

Also improved other stuff in the add-on:
- add the GPL license block to create_widgets.py;
- rename "arm[ature]" to "rig" in some functions, for consistency and
  to avoid confusion with the crane's arm;
- changes to the UI panel:
  - remove the boxes,
  - put focal length at the top of the panel,
  - group related properties using aligned columns,
  - change the Make Camera Active operator's poll method, so that it
    is always visible in the UI, but greyed out when the camera is
    already active.
parent cebfa3b6
Branches
Tags
No related merge requests found
...@@ -18,8 +18,8 @@ ...@@ -18,8 +18,8 @@
bl_info = { bl_info = {
"name": "Add Camera Rigs", "name": "Add Camera Rigs",
"author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard", "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard, Flavio Perez",
"version": (1, 4, 2), "version": (1, 4, 3),
"blender": (2, 80, 0), "blender": (2, 80, 0),
"location": "View3D > Add > Camera > Dolly or Crane Rig", "location": "View3D > Add > Camera > Dolly or Crane Rig",
"description": "Adds a Camera Rig with UI", "description": "Adds a Camera Rig with UI",
......
...@@ -17,18 +17,33 @@ ...@@ -17,18 +17,33 @@
# ##### END GPL LICENSE BLOCK ##### # ##### END GPL LICENSE BLOCK #####
import bpy import bpy
from bpy_extras import object_utils
from bpy.types import Operator from bpy.types import Operator
from math import radians, pi from bpy_extras import object_utils
from mathutils import Vector
from rna_prop_ui import rna_idprop_ui_prop_get from rna_prop_ui import rna_idprop_ui_prop_get
from math import pi
from .create_widgets import (create_root_widget, from .create_widgets import (create_root_widget,
create_widget, create_camera_widget, create_aim_widget,
create_camera_widget, create_circle_widget, create_corner_widget)
create_aim_widget,
)
def create_prop_driver(rig, cam, prop_from, prop_to):
"""Create driver to a property on the rig"""
driver = cam.data.driver_add(prop_to)
driver.driver.type = 'SCRIPTED'
var = driver.driver.variables.new()
var.name = 'var'
var.type = 'SINGLE_PROP'
# Target the custom bone property
var.targets[0].id = rig
var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from
driver.driver.expression = 'var'
def create_dolly_bones(rig, bone_layers): def create_dolly_bones(rig):
"""Create bones for the dolly camera rig"""
bones = rig.data.edit_bones bones = rig.data.edit_bones
# Add new bones # Add new bones
...@@ -39,7 +54,7 @@ def create_dolly_bones(rig, bone_layers): ...@@ -39,7 +54,7 @@ def create_dolly_bones(rig, bone_layers):
ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child = bones.new("Aim_shape_rotation-MCH")
ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.head = (0.0, 10.0, 1.7)
ctrl_aim_child.tail = (0.0, 11.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7)
ctrl_aim_child.layers = bone_layers ctrl_aim_child.layers = tuple(i == 1 for i in range(32))
ctrl_aim = bones.new("Aim") ctrl_aim = bones.new("Aim")
ctrl_aim.head = (0.0, 10.0, 1.7) ctrl_aim.head = (0.0, 10.0, 1.7)
...@@ -57,7 +72,8 @@ def create_dolly_bones(rig, bone_layers): ...@@ -57,7 +72,8 @@ def create_dolly_bones(rig, bone_layers):
ctrl_aim_child.parent = ctrl_aim ctrl_aim_child.parent = ctrl_aim
def create_crane_bones(rig, bone_layers): def create_crane_bones(rig):
"""Create bones for the crane camera rig"""
bones = rig.data.edit_bones bones = rig.data.edit_bones
# Add new bones # Add new bones
...@@ -68,7 +84,7 @@ def create_crane_bones(rig, bone_layers): ...@@ -68,7 +84,7 @@ def create_crane_bones(rig, bone_layers):
ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child = bones.new("Aim_shape_rotation-MCH")
ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.head = (0.0, 10.0, 1.7)
ctrl_aim_child.tail = (0.0, 11.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7)
ctrl_aim_child.layers = bone_layers ctrl_aim_child.layers = tuple(i == 1 for i in range(32))
ctrl_aim = bones.new("Aim") ctrl_aim = bones.new("Aim")
ctrl_aim.head = (0.0, 10.0, 1.7) ctrl_aim.head = (0.0, 10.0, 1.7)
...@@ -112,34 +128,13 @@ def create_crane_bones(rig, bone_layers): ...@@ -112,34 +128,13 @@ def create_crane_bones(rig, bone_layers):
pose_bones["Crane_height"].lock_scale = (True, False, True) pose_bones["Crane_height"].lock_scale = (True, False, True)
def build_camera_rig(context, mode): def setup_3d_rig(rig, cam):
bone_layers = tuple(i == 1 for i in range(32)) """Finish setting up Dolly and Crane rigs"""
view_layer = bpy.context.view_layer
rig_name = mode.capitalize() + "_Rig"
rig_data = bpy.data.armatures.new(rig_name)
rig = object_utils.object_data_add(context, rig_data, name=rig_name)
rig["rig_id"] = "%s" % rig_name
view_layer.objects.active = rig
rig.location = context.scene.cursor.location
bpy.ops.object.mode_set(mode='EDIT')
# Add new bones
if mode == "DOLLY":
create_dolly_bones(rig, bone_layers)
elif mode == "CRANE":
create_crane_bones(rig, bone_layers)
# Jump into object mode and change bones to euler # Jump into object mode and change bones to euler
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
pose_bones = rig.pose.bones pose_bones = rig.pose.bones
for b in pose_bones: for bone in pose_bones:
b.rotation_mode = 'XYZ' bone.rotation_mode = 'XYZ'
# Add custom properties to the armature’s Camera bone,
# so that all properties may be animated in a single action
# Add driver after the camera is created
# Lens property # Lens property
pb = pose_bones['Camera'] pb = pose_bones['Camera']
...@@ -150,22 +145,6 @@ def build_camera_rig(context, mode): ...@@ -150,22 +145,6 @@ def build_camera_rig(context, mode):
prop["max"] = 1000000.0 prop["max"] = 1000000.0
prop["soft_max"] = 5000.0 prop["soft_max"] = 5000.0
# DOF Focus Distance property
pb = pose_bones['Camera']
pb["focus_distance"] = 10.0
prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True)
prop["default"] = 10.0
prop["min"] = 0.0
# DOF F-Stop property
pb = pose_bones['Camera']
pb["aperture_fstop"] = 2.8
prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True)
prop["default"] = 2.8
prop["min"] = 0.0
prop["soft_min"] = 0.1
prop["soft_max"] = 128.0
# Build the widgets # Build the widgets
root_widget = create_root_widget("Camera_Root") root_widget = create_root_widget("Camera_Root")
camera_widget = create_camera_widget("Camera") camera_widget = create_camera_widget("Camera")
...@@ -176,7 +155,7 @@ def build_camera_rig(context, mode): ...@@ -176,7 +155,7 @@ def build_camera_rig(context, mode):
pose_bones["Aim"].custom_shape = aim_widget pose_bones["Aim"].custom_shape = aim_widget
pose_bones["Camera"].custom_shape = camera_widget pose_bones["Camera"].custom_shape = camera_widget
# Set the "At" field to the child # Set the "At" field to the shape mecanism
pose_bones["Aim"].custom_shape_transform = pose_bones["Aim_shape_rotation-MCH"] pose_bones["Aim"].custom_shape_transform = pose_bones["Aim_shape_rotation-MCH"]
# Add constraints to bones # Add constraints to bones
...@@ -189,55 +168,359 @@ def build_camera_rig(context, mode): ...@@ -189,55 +168,359 @@ def build_camera_rig(context, mode):
con.subtarget = "Aim" con.subtarget = "Aim"
con.use_target_z = True con.use_target_z = True
# Change display to BBone: it just looks nicer cam.data.display_size = 1.0
bpy.context.object.data.display_type = 'BBONE' cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x
# Change display to wire for object
bpy.context.object.display_type = 'WIRE' create_prop_driver(rig, cam, "lens", "lens")
def create_2d_bones(context, rig, cam):
"""Create bones for the 2D camera rig"""
scene = context.scene
bones = rig.data.edit_bones
# Add new bones
bones = rig.data.edit_bones
root = bones.new("Root")
root.tail = Vector((0.0, 0.0, 1.0))
root.show_wire = True
ctrl = bones.new('Camera')
ctrl.tail = Vector((0.0, 0.0, 1.0))
ctrl.show_wire = True
left_corner = bones.new("Left_corner")
left_corner.head = (-3, 10, -2)
left_corner.tail = left_corner.head + Vector((0.0, 0.0, 1.0))
left_corner.show_wire = True
right_corner = bones.new("Right_corner")
right_corner.head = (3, 10, -2)
right_corner.tail = right_corner.head + Vector((0.0, 0.0, 1.0))
right_corner.show_wire = True
corner_distance_x = (left_corner.head - right_corner.head).length
corner_distance_y = -left_corner.head.z
corner_distance_z = left_corner.head.y
center = bones.new("Center-MCH")
center.head = ((right_corner.head + left_corner.head) / 2.0)
center.tail = center.head + Vector((0.0, 0.0, 1.0))
center.layers = tuple(i == 1 for i in range(32))
center.show_wire = True
# Setup hierarchy
ctrl.parent = root
left_corner.parent = root
right_corner.parent = root
center.parent = root
# Jump into object mode and change bones to euler
bpy.ops.object.mode_set(mode='OBJECT')
pose_bones = rig.pose.bones
for bone in pose_bones:
bone.rotation_mode = 'XYZ'
# Bone drivers
center_drivers = pose_bones["Center-MCH"].driver_add("location")
# Center X driver
driver = center_drivers[0].driver
driver.type = 'AVERAGE'
for corner in ('left', 'right'):
var = driver.variables.new()
var.name = corner
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + '_corner'
var.targets[0].transform_type = 'LOC_X'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
# Center Y driver
driver = center_drivers[1].driver
driver.type = 'SCRIPTED'
driver.expression = '({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + (left_y + right_y)/2'.format(distance_x=corner_distance_x)
for direction in ('x', 'y'):
for corner in ('left', 'right'):
var = driver.variables.new()
var.name = '%s_%s' % (corner, direction)
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + '_corner'
var.targets[0].transform_type = 'LOC_' + direction.upper()
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = 'res_' + direction
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'SCENE'
var.targets[0].id = scene
var.targets[0].data_path = 'render.resolution_' + direction
# Center Z driver
driver = center_drivers[2].driver
driver.type = 'AVERAGE'
for corner in ('left', 'right'):
var = driver.variables.new()
var.name = corner
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + '_corner'
var.targets[0].transform_type = 'LOC_Z'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
# Bone constraints
con = pose_bones["Camera"].constraints.new('DAMPED_TRACK')
con.target = rig
con.subtarget = "Center-MCH"
con.track_axis = 'TRACK_NEGATIVE_Z'
# Build the widgets
left_widget = create_corner_widget("Left_corner", reverse=True)
right_widget = create_corner_widget("Right_corner")
parent_widget = create_circle_widget("Root", radius=0.5)
camera_widget = create_circle_widget("Camera_2D", radius=0.3)
# Add the custom bone shapes
pose_bones["Left_corner"].custom_shape = left_widget
pose_bones["Right_corner"].custom_shape = right_widget
pose_bones["Root"].custom_shape = parent_widget
pose_bones["Camera"].custom_shape = camera_widget
# Lock the relevant loc, rot and scale
pose_bones["Left_corner"].lock_rotation = (True,) * 3
pose_bones["Right_corner"].lock_rotation = (True,) * 3
pose_bones["Camera"].lock_rotation = (True,) * 3
pose_bones["Camera"].lock_scale = (True,) * 3
# Camera settings
cam.data.sensor_fit = "HORIZONTAL" # Avoids distortion in portrait format
# Property to switch between rotation and switch mode
pose_bones["Camera"]['rotation_shift'] = 0.0
prop = rna_idprop_ui_prop_get(pose_bones["Camera"], 'rotation_shift', create=True)
prop["min"] = 0.0
prop["max"] = 1.0
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
prop["description"] = 'rotation_shift'
# Rotation / shift switch driver
driver = con.driver_add('influence').driver
driver.expression = '1 - rotation_shift'
var = driver.variables.new()
var.name = 'rotation_shift'
var.type = 'SINGLE_PROP'
var.targets[0].id = rig
var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]'
# Focal length driver
driver = cam.data.driver_add('lens').driver
driver.expression = 'abs({distance_z} - (left_z + right_z)/2 + cam_z) * 36 / frame_width'.format(distance_z=corner_distance_z)
var = driver.variables.new()
var.name = 'frame_width'
var.type = 'LOC_DIFF'
var.targets[0].id = rig
var.targets[0].bone_target = "Left_corner"
var.targets[0].transform_space = 'WORLD_SPACE'
var.targets[1].id = rig
var.targets[1].bone_target = "Right_corner"
var.targets[1].transform_space = 'WORLD_SPACE'
for corner in ('left', 'right'):
var = driver.variables.new()
var.name = corner + '_z'
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + '_corner'
var.targets[0].transform_type = 'LOC_Z'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = 'cam_z'
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Camera"
var.targets[0].transform_type = 'LOC_Z'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
# Orthographic scale driver
driver = cam.data.driver_add('ortho_scale').driver
driver.expression = 'abs({distance_x} - (left_x - right_x))'.format(distance_x=corner_distance_x)
for corner in ('left', 'right'):
var = driver.variables.new()
var.name = corner + '_x'
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + '_corner'
var.targets[0].transform_type = 'LOC_X'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
# Shift driver X
driver = cam.data.driver_add('shift_x').driver
driver.expression = 'rotation_shift * (((left_x + right_x)/2 - cam_x) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36)'.format(distance_z=corner_distance_z)
var = driver.variables.new()
var.name = 'rotation_shift'
var.type = 'SINGLE_PROP'
var.targets[0].id = rig
var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]'
for direction in ('x', 'z'):
for corner in ('left', 'right'):
var = driver.variables.new()
var.name = '%s_%s' % (corner, direction)
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + '_corner'
var.targets[0].transform_type = 'LOC_' + direction.upper()
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = 'cam_' + direction
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Camera"
var.targets[0].transform_type = 'LOC_' + direction.upper()
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = 'lens'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'CAMERA'
var.targets[0].id = cam.data
var.targets[0].data_path = 'lens'
# Shift driver Y
driver = cam.data.driver_add('shift_y').driver
driver.expression = 'rotation_shift * -(({distance_y} - (left_y + right_y)/2 + cam_y) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36 - (res_y/res_x)/2)'.format(distance_y=corner_distance_y, distance_z=corner_distance_z)
var = driver.variables.new()
var.name = 'rotation_shift'
var.type = 'SINGLE_PROP'
var.targets[0].id = rig
var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]'
for direction in ('y', 'z'):
for corner in ('left', 'right'):
var = driver.variables.new()
var.name = '%s_%s' % (corner, direction)
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + '_corner'
var.targets[0].transform_type = 'LOC_' + direction.upper()
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = 'cam_' + direction
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Camera"
var.targets[0].transform_type = 'LOC_' + direction.upper()
var.targets[0].transform_space = 'TRANSFORM_SPACE'
for direction in ('x', 'y'):
var = driver.variables.new()
var.name = 'res_' + direction
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'SCENE'
var.targets[0].id = scene
var.targets[0].data_path = 'render.resolution_' + direction
var = driver.variables.new()
var.name = 'lens'
var.type = 'SINGLE_PROP'
var.targets[0].id_type = 'CAMERA'
var.targets[0].id = cam.data
var.targets[0].data_path = 'lens'
def build_camera_rig(context, mode):
"""Create stuff common to all camera rigs."""
# Add the camera object # Add the camera object
cam_name = "%s_Camera" % mode.capitalize() cam_name = "%s_Camera" % mode.capitalize()
cam_data = bpy.data.cameras.new(cam_name) cam_data = bpy.data.cameras.new(cam_name)
cam = object_utils.object_data_add(context, cam_data, name=cam_name) cam = object_utils.object_data_add(context, cam_data, name=cam_name)
view_layer.objects.active = cam
context.scene.camera = cam context.scene.camera = cam
cam.data.display_size = 1.0 # Add the rig object
cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x rig_name = mode.capitalize() + "_Rig"
rig_data = bpy.data.armatures.new(rig_name)
rig = object_utils.object_data_add(context, rig_data, name=rig_name)
rig["rig_id"] = "%s" % rig_name
rig.location = context.scene.cursor.location
bpy.ops.object.mode_set(mode='EDIT')
# Add new bones and setup specific rigs
if mode == "DOLLY":
create_dolly_bones(rig)
setup_3d_rig(rig, cam)
elif mode == "CRANE":
create_crane_bones(rig)
setup_3d_rig(rig, cam)
elif mode == "2D":
create_2d_bones(context, rig, cam)
# Parent the camera to the rig
cam.location = (0.0, -1.0, 0.0) # Move the camera to the correct position cam.location = (0.0, -1.0, 0.0) # Move the camera to the correct position
cam.parent = rig cam.parent = rig
cam.parent_type = "BONE" cam.parent_type = "BONE"
cam.parent_bone = "Camera" cam.parent_bone = "Camera"
# Change display to BBone: it just looks nicer
rig.data.display_type = 'BBONE'
# Change display to wire for object
rig.display_type = 'WIRE'
# Lock camera transforms # Lock camera transforms
cam.lock_location = (True,) * 3 cam.lock_location = (True,) * 3
cam.lock_rotation = (True,) * 3 cam.lock_rotation = (True,) * 3
cam.lock_scale = (True,) * 3 cam.lock_scale = (True,) * 3
# Add custom properties to the armature’s Camera bone,
# so that all properties may be animated in a single action
pose_bones = rig.pose.bones
# DOF Focus Distance property
pb = pose_bones['Camera']
pb["focus_distance"] = 10.0
prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True)
prop["default"] = 10.0
prop["min"] = 0.0
# DOF F-Stop property
pb = pose_bones['Camera']
pb["aperture_fstop"] = 2.8
prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True)
prop["default"] = 2.8
prop["min"] = 0.0
prop["soft_min"] = 0.1
prop["soft_max"] = 128.0
# Add drivers to link the camera properties to the custom props # Add drivers to link the camera properties to the custom props
# on the armature # on the armature
for prop_from, prop_to in (("lens", "lens"), create_prop_driver(rig, cam, "focus_distance", "dof.focus_distance")
("focus_distance", "dof.focus_distance"), create_prop_driver(rig, cam, "aperture_fstop", "dof.aperture_fstop")
("aperture_fstop", "dof.aperture_fstop")):
driver = cam.data.driver_add(prop_to)
driver.driver.type = 'SCRIPTED'
var = driver.driver.variables.new()
var.name = 'var'
var.type = 'SINGLE_PROP'
# Target the custom bone property
var.targets[0].id = rig
var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from
driver.driver.expression = 'var'
# Make the rig the active object # Make the rig the active object
for ob in view_layer.objects: view_layer = context.view_layer
ob.select_set(False) for obj in view_layer.objects:
obj.select_set(False)
rig.select_set(True) rig.select_set(True)
view_layer.objects.active = rig view_layer.objects.active = rig
return rig
class OBJECT_OT_build_camera_rig(Operator): class OBJECT_OT_build_camera_rig(Operator):
bl_idname = "object.build_camera_rig" bl_idname = "object.build_camera_rig"
...@@ -245,11 +528,12 @@ class OBJECT_OT_build_camera_rig(Operator): ...@@ -245,11 +528,12 @@ class OBJECT_OT_build_camera_rig(Operator):
bl_description = "Build a Camera Rig" bl_description = "Build a Camera Rig"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
mode: bpy.props.EnumProperty(items= mode: bpy.props.EnumProperty(items=(('DOLLY', 'Dolly', 'Dolly rig'),
(('DOLLY',) * 3, ('CRANE', 'Crane', 'Crane rig',),
('CRANE',) * 3,), ('2D', '2D', '2D rig')),
name="mode", name="mode",
description="", default="DOLLY") description="Type of camera to create",
default="DOLLY")
def execute(self, context): def execute(self, context):
# Build the rig # Build the rig
...@@ -260,18 +544,23 @@ class OBJECT_OT_build_camera_rig(Operator): ...@@ -260,18 +544,23 @@ class OBJECT_OT_build_camera_rig(Operator):
def add_dolly_crane_buttons(self, context): def add_dolly_crane_buttons(self, context):
"""Dolly and crane entries in the Add Object > Camera Menu""" """Dolly and crane entries in the Add Object > Camera Menu"""
if context.mode == 'OBJECT': if context.mode == 'OBJECT':
op = self.layout.operator( self.layout.operator(
OBJECT_OT_build_camera_rig.bl_idname, OBJECT_OT_build_camera_rig.bl_idname,
text="Dolly Camera Rig", text="Dolly Camera Rig",
icon='CAMERA_DATA' icon='VIEW_CAMERA'
) ).mode = "DOLLY"
op.mode = "DOLLY"
op = self.layout.operator( self.layout.operator(
OBJECT_OT_build_camera_rig.bl_idname, OBJECT_OT_build_camera_rig.bl_idname,
text="Crane Camera Rig", text="Crane Camera Rig",
icon='CAMERA_DATA' icon='VIEW_CAMERA'
) ).mode = "CRANE"
op.mode = "CRANE"
self.layout.operator(
OBJECT_OT_build_camera_rig.bl_idname,
text="2D Camera Rig",
icon='PIVOT_BOUNDBOX'
).mode = "2D"
classes = ( classes = (
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
import bpy import bpy
from bpy.types import Panel from bpy.types import Panel
from .operators import get_arm_and_cam from .operators import get_rig_and_cam
class ADD_CAMERA_RIGS_PT_composition_guides(Panel): class ADD_CAMERA_RIGS_PT_composition_guides(Panel):
bl_label = "Composition Guides" bl_label = "Composition Guides"
...@@ -29,7 +29,7 @@ class ADD_CAMERA_RIGS_PT_composition_guides(Panel): ...@@ -29,7 +29,7 @@ class ADD_CAMERA_RIGS_PT_composition_guides(Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
arm, cam = get_arm_and_cam(context.active_object) rig, cam = get_rig_and_cam(context.active_object)
cam = cam.data cam = cam.data
layout.prop(cam, "show_safe_areas") layout.prop(cam, "show_safe_areas")
......
# ##### 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 import bpy
from mathutils import Vector
from math import cos, sin, pi
def create_widget(name): def create_widget(name):
...@@ -29,6 +49,44 @@ def create_widget(name): ...@@ -29,6 +49,44 @@ def create_widget(name):
return obj return obj
def create_corner_widget(name, reverse=False):
"""Create a wedge-shaped widget"""
obj = create_widget(name)
if not obj.data.vertices:
reverse = -1 if reverse else 1
verts = (Vector((reverse * 0.0, 0.0, 0.0)),
Vector((reverse * 0.0, 1.0, 0.0)),
Vector((reverse * -0.1, 1.0, 0.0)),
Vector((reverse * -0.1, 0.1, 0.0)),
Vector((reverse * -1.0, 0.1, 0.0)),
Vector((reverse * -1.0, 0.0, 0.0)),
)
edges = [(n, (n+1) % len(verts)) for n in range(len(verts))]
mesh = obj.data
mesh.from_pydata(verts, edges, ())
mesh.update()
return obj
def create_circle_widget(name, radius=1.0):
"""Create a circle-shaped widget"""
obj = create_widget(name)
if not obj.data.vertices:
vert_n = 16
verts = []
for n in range(vert_n):
angle = n / vert_n * 2*pi
verts.append(Vector((cos(angle) * radius,
sin(angle) * radius, 0.0)))
edges = [(n, (n+1) % len(verts)) for n in range(len(verts))]
mesh = obj.data
mesh.from_pydata(verts, edges, ())
mesh.update()
return obj
def create_root_widget(name): def create_root_widget(name):
"""Create a compass-shaped widget""" """Create a compass-shaped widget"""
obj = create_widget(name) obj = create_widget(name)
......
...@@ -20,7 +20,7 @@ import bpy ...@@ -20,7 +20,7 @@ import bpy
from bpy.types import Operator from bpy.types import Operator
def get_arm_and_cam(obj): def get_rig_and_cam(obj):
if obj.type == 'ARMATURE': if obj.type == 'ARMATURE':
cam = None cam = None
for child in obj.children: for child in obj.children:
...@@ -32,7 +32,8 @@ def get_arm_and_cam(obj): ...@@ -32,7 +32,8 @@ def get_arm_and_cam(obj):
elif (obj.type == 'CAMERA' elif (obj.type == 'CAMERA'
and obj.parent is not None and obj.parent is not None
and "rig_id" in obj.parent and "rig_id" in obj.parent
and obj.parent["rig_id"].lower() in {"dolly_rig", "crane_rig"}): and obj.parent["rig_id"].lower() in {"dolly_rig",
"crane_rig", "2d_rig"}):
return obj.parent, obj return obj.parent, obj
return None, None return None, None
...@@ -41,26 +42,32 @@ class CameraRigMixin(): ...@@ -41,26 +42,32 @@ class CameraRigMixin():
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
if context.active_object is not None: if context.active_object is not None:
return get_arm_and_cam(context.active_object) != (None, None) return get_rig_and_cam(context.active_object) != (None, None)
return False return False
class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator, CameraRigMixin): class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator):
bl_idname = "add_camera_rigs.set_scene_camera" bl_idname = "add_camera_rigs.set_scene_camera"
bl_label = "Make Camera Active" bl_label = "Make Camera Active"
bl_description = "Makes the camera parented to this rig the active scene camera" bl_description = "Makes the camera parented to this rig the active scene camera"
@classmethod
def poll(cls, context):
if context.active_object is not None:
rig, cam = get_rig_and_cam(context.active_object)
if cam is not None:
return cam is not context.scene.camera
return False
def execute(self, context): def execute(self, context):
arm, cam = get_arm_and_cam(context.active_object) rig, cam = get_rig_and_cam(context.active_object)
scene_cam = context.scene.camera scene_cam = context.scene.camera
if cam is not None and cam is not scene_cam:
context.scene.camera = cam context.scene.camera = cam
return {'FINISHED'} return {'FINISHED'}
return {'CANCELLED'}
class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin): class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin):
bl_idname = "add_camera_rigs.add_marker_bind" bl_idname = "add_camera_rigs.add_marker_bind"
...@@ -68,7 +75,7 @@ class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin): ...@@ -68,7 +75,7 @@ class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin):
bl_description = "Add marker to current frame then bind rig camera to it (for camera switching)" bl_description = "Add marker to current frame then bind rig camera to it (for camera switching)"
def execute(self, context): def execute(self, context):
arm, cam = get_arm_and_cam(context.active_object) rig, cam = get_rig_and_cam(context.active_object)
marker = context.scene.timeline_markers.new( marker = context.scene.timeline_markers.new(
"cam_" + str(context.scene.frame_current), "cam_" + str(context.scene.frame_current),
...@@ -85,15 +92,15 @@ class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin): ...@@ -85,15 +92,15 @@ class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin):
bl_description = "Create Empty and add as DOF Object" bl_description = "Create Empty and add as DOF Object"
def execute(self, context): def execute(self, context):
arm, cam = get_arm_and_cam(context.active_object) rig, cam = get_rig_and_cam(context.active_object)
bone = arm.data.bones['Aim_shape_rotation-MCH'] bone = rig.data.bones['Aim_shape_rotation-MCH']
# Add Empty # Add Empty
empty_obj = bpy.data.objects.new("EmptyDOF", None) empty_obj = bpy.data.objects.new("EmptyDOF", None)
context.scene.collection.objects.link(empty_obj) context.scene.collection.objects.link(empty_obj)
# Parent to Aim Child bone # Parent to Aim Child bone
empty_obj.parent = arm empty_obj.parent = rig
empty_obj.parent_type = "BONE" empty_obj.parent_type = "BONE"
empty_obj.parent_bone = "Aim_shape_rotation-MCH" empty_obj.parent_bone = "Aim_shape_rotation-MCH"
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
import bpy import bpy
from bpy.types import Panel from bpy.types import Panel
from .operators import get_arm_and_cam, CameraRigMixin from .operators import get_rig_and_cam, CameraRigMixin
class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin): class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin):
...@@ -30,67 +30,88 @@ class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin): ...@@ -30,67 +30,88 @@ class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin):
def draw(self, context): def draw(self, context):
active_object = context.active_object active_object = context.active_object
arm, cam = get_arm_and_cam(context.active_object) rig, cam = get_rig_and_cam(context.active_object)
pose_bones = arm.pose.bones pose_bones = rig.pose.bones
cam_data = cam.data cam_data = cam.data
layout = self.layout
# Camera lens
if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"):
layout.prop(pose_bones["Camera"], '["lens"]',
text="Focal Length (mm)")
col = layout.column(align=True)
col.label(text="Clipping:")
col.prop(cam_data, "clip_start", text="Start")
col.prop(cam_data, "clip_end", text="End")
layout = self.layout.box().column()
layout.label(text="Clipping:")
layout.prop(cam_data, "clip_start", text="Start")
layout.prop(cam_data, "clip_end", text="End")
layout.prop(cam_data, "type") layout.prop(cam_data, "type")
layout.prop(cam_data.dof, "use_dof")
# DoF
col = layout.column(align=True)
col.prop(cam_data.dof, "use_dof")
if cam_data.dof.use_dof: if cam_data.dof.use_dof:
if rig["rig_id"].lower() in ("crane_rig", "dolly_rig"):
if cam_data.dof.focus_object is None: if cam_data.dof.focus_object is None:
layout.operator("add_camera_rigs.add_dof_object", col.operator("add_camera_rigs.add_dof_object",
text="Add DOF Empty", icon="OUTLINER_OB_EMPTY") text="Add DOF Empty", icon="OUTLINER_OB_EMPTY")
layout.prop(pose_bones["Camera"], else:
col.prop(cam_data.dof, "focus_object")
row = col.row(align=True)
row.active = cam_data.dof.focus_object is None
row.prop(pose_bones["Camera"],
'["focus_distance"]', text="Focus Distance") '["focus_distance"]', text="Focus Distance")
layout.prop(pose_bones["Camera"], col.prop(pose_bones["Camera"],
'["aperture_fstop"]', text="F-Stop") '["aperture_fstop"]', text="F-Stop")
# Viewport display
layout.prop(active_object, 'show_in_front', layout.prop(active_object, 'show_in_front',
toggle=False, text='Show in Front') toggle=False, text='Show in Front')
layout.prop(cam_data, "show_limits") layout.prop(cam_data, "show_limits")
layout.prop(cam_data, "show_passepartout") col = layout.column(align=True)
col.prop(cam_data, "show_passepartout")
if cam_data.show_passepartout: if cam_data.show_passepartout:
layout.prop(cam_data, "passepartout_alpha") col.prop(cam_data, "passepartout_alpha")
layout.row().separator() # Composition guides
# Added the comp guides here
layout.popover( layout.popover(
panel="ADD_CAMERA_RIGS_PT_composition_guides", panel="ADD_CAMERA_RIGS_PT_composition_guides",
text="Composition Guides",) text="Composition Guides",)
layout.row().separator()
layout.prop(cam, # Props and operators
col = layout.column(align=True)
col.prop(cam,
"hide_select", text="Make Camera Unselectable") "hide_select", text="Make Camera Unselectable")
col.operator("add_camera_rigs.add_marker_bind",
layout.operator("add_camera_rigs.add_marker_bind",
text="Add Marker and Bind", icon="MARKER_HLT") text="Add Marker and Bind", icon="MARKER_HLT")
if context.scene.camera is not cam: col.operator("add_camera_rigs.set_scene_camera",
layout.operator("add_camera_rigs.set_scene_camera",
text="Make Camera Active", icon='CAMERA_DATA') text="Make Camera Active", icon='CAMERA_DATA')
# Camera lens if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"):
layout.separator()
layout.prop(pose_bones["Camera"], '["lens"]', text="Focal Length (mm)")
# Track to Constraint # Track to Constraint
layout.label(text="Tracking:") col = layout.column(align=True)
layout.prop(pose_bones["Camera"].constraints["Track To"], col.label(text="Tracking:")
col.prop(pose_bones["Camera"].constraints["Track To"],
'influence', text="Aim Lock", slider=True) 'influence', text="Aim Lock", slider=True)
if arm["rig_id"].lower() == "crane_rig":
col = layout.box().column()
# Crane arm stuff # Crane arm stuff
if rig["rig_id"].lower() == "crane_rig":
col = layout.column(align=True)
col.label(text="Crane Arm:") col.label(text="Crane Arm:")
col.prop(pose_bones["Crane_height"], col.prop(pose_bones["Crane_height"],
'scale', index=1, text="Arm Height") 'scale', index=1, text="Arm Height")
col.prop(pose_bones["Crane_arm"], col.prop(pose_bones["Crane_arm"],
'scale', index=1, text="Arm Length") 'scale', index=1, text="Arm Length")
# 2D rig stuff
elif rig["rig_id"].lower() == "2d_rig":
col = layout.column(align=True)
col.label(text="2D Rig:")
col.prop(pose_bones["Camera"], '["rotation_shift"]',
text="Rotation/Shift")
if cam.data.sensor_width != 36:
col.label(text="Please set Camera Sensor Width to 36", icon="ERROR")
def register(): def register():
bpy.utils.register_class(ADD_CAMERA_RIGS_PT_camera_rig_ui) bpy.utils.register_class(ADD_CAMERA_RIGS_PT_camera_rig_ui)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment