Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien Picard <dam.pic@free.fr>2020-01-24 12:30:51 +0300
committerDamien Picard <dam.pic@free.fr>2020-03-09 14:07:23 +0300
commitf4cf9b00db36bf096e1cf5ad50127ade8b985dd2 (patch)
tree794eda051cf81946121337f110ad7d24a979cd8c
parentcebfa3b6e6f4a436a937baf9549963dde2dedc6c (diff)
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.
-rw-r--r--add_camera_rigs/__init__.py4
-rw-r--r--add_camera_rigs/build_rigs.py465
-rw-r--r--add_camera_rigs/composition_guides_menu.py4
-rw-r--r--add_camera_rigs/create_widgets.py58
-rw-r--r--add_camera_rigs/operators.py35
-rw-r--r--add_camera_rigs/ui_panels.py111
6 files changed, 526 insertions, 151 deletions
diff --git a/add_camera_rigs/__init__.py b/add_camera_rigs/__init__.py
index 6b577199..6f9f9eeb 100644
--- a/add_camera_rigs/__init__.py
+++ b/add_camera_rigs/__init__.py
@@ -18,8 +18,8 @@
bl_info = {
"name": "Add Camera Rigs",
- "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard",
- "version": (1, 4, 2),
+ "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard, Flavio Perez",
+ "version": (1, 4, 3),
"blender": (2, 80, 0),
"location": "View3D > Add > Camera > Dolly or Crane Rig",
"description": "Adds a Camera Rig with UI",
diff --git a/add_camera_rigs/build_rigs.py b/add_camera_rigs/build_rigs.py
index 1a37bc6e..57cadc0e 100644
--- a/add_camera_rigs/build_rigs.py
+++ b/add_camera_rigs/build_rigs.py
@@ -17,18 +17,33 @@
# ##### END GPL LICENSE BLOCK #####
import bpy
-from bpy_extras import object_utils
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 math import pi
+
from .create_widgets import (create_root_widget,
- create_widget,
- create_camera_widget,
- create_aim_widget,
- )
+ create_camera_widget, create_aim_widget,
+ create_circle_widget, create_corner_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
# Add new bones
@@ -39,7 +54,7 @@ def create_dolly_bones(rig, bone_layers):
ctrl_aim_child = bones.new("Aim_shape_rotation-MCH")
ctrl_aim_child.head = (0.0, 10.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.head = (0.0, 10.0, 1.7)
@@ -57,7 +72,8 @@ def create_dolly_bones(rig, bone_layers):
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
# Add new bones
@@ -68,7 +84,7 @@ def create_crane_bones(rig, bone_layers):
ctrl_aim_child = bones.new("Aim_shape_rotation-MCH")
ctrl_aim_child.head = (0.0, 10.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.head = (0.0, 10.0, 1.7)
@@ -112,34 +128,13 @@ def create_crane_bones(rig, bone_layers):
pose_bones["Crane_height"].lock_scale = (True, False, True)
-def build_camera_rig(context, mode):
- bone_layers = tuple(i == 1 for i in range(32))
- 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)
-
+def setup_3d_rig(rig, cam):
+ """Finish setting up Dolly and Crane rigs"""
# Jump into object mode and change bones to euler
bpy.ops.object.mode_set(mode='OBJECT')
pose_bones = rig.pose.bones
- for b in pose_bones:
- b.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
+ for bone in pose_bones:
+ bone.rotation_mode = 'XYZ'
# Lens property
pb = pose_bones['Camera']
@@ -150,22 +145,6 @@ def build_camera_rig(context, mode):
prop["max"] = 1000000.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
root_widget = create_root_widget("Camera_Root")
camera_widget = create_camera_widget("Camera")
@@ -176,7 +155,7 @@ def build_camera_rig(context, mode):
pose_bones["Aim"].custom_shape = aim_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"]
# Add constraints to bones
@@ -189,55 +168,359 @@ def build_camera_rig(context, mode):
con.subtarget = "Aim"
con.use_target_z = True
- # Change display to BBone: it just looks nicer
- bpy.context.object.data.display_type = 'BBONE'
- # Change display to wire for object
- bpy.context.object.display_type = 'WIRE'
+ cam.data.display_size = 1.0
+ cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x
+
+ 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
cam_name = "%s_Camera" % mode.capitalize()
cam_data = bpy.data.cameras.new(cam_name)
cam = object_utils.object_data_add(context, cam_data, name=cam_name)
- view_layer.objects.active = cam
context.scene.camera = cam
- cam.data.display_size = 1.0
- cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x
+ # Add the rig object
+ 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.parent = rig
cam.parent_type = "BONE"
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
cam.lock_location = (True,) * 3
cam.lock_rotation = (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
# on the armature
- for prop_from, prop_to in (("lens", "lens"),
- ("focus_distance", "dof.focus_distance"),
- ("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'
+ create_prop_driver(rig, cam, "focus_distance", "dof.focus_distance")
+ create_prop_driver(rig, cam, "aperture_fstop", "dof.aperture_fstop")
# Make the rig the active object
- for ob in view_layer.objects:
- ob.select_set(False)
+ view_layer = context.view_layer
+ for obj in view_layer.objects:
+ obj.select_set(False)
rig.select_set(True)
view_layer.objects.active = rig
- return rig
-
class OBJECT_OT_build_camera_rig(Operator):
bl_idname = "object.build_camera_rig"
@@ -245,11 +528,12 @@ class OBJECT_OT_build_camera_rig(Operator):
bl_description = "Build a Camera Rig"
bl_options = {'REGISTER', 'UNDO'}
- mode: bpy.props.EnumProperty(items=
- (('DOLLY',) * 3,
- ('CRANE',) * 3,),
+ mode: bpy.props.EnumProperty(items=(('DOLLY', 'Dolly', 'Dolly rig'),
+ ('CRANE', 'Crane', 'Crane rig',),
+ ('2D', '2D', '2D rig')),
name="mode",
- description="", default="DOLLY")
+ description="Type of camera to create",
+ default="DOLLY")
def execute(self, context):
# Build the rig
@@ -260,18 +544,23 @@ class OBJECT_OT_build_camera_rig(Operator):
def add_dolly_crane_buttons(self, context):
"""Dolly and crane entries in the Add Object > Camera Menu"""
if context.mode == 'OBJECT':
- op = self.layout.operator(
+ self.layout.operator(
OBJECT_OT_build_camera_rig.bl_idname,
text="Dolly Camera Rig",
- icon='CAMERA_DATA'
- )
- op.mode = "DOLLY"
- op = self.layout.operator(
+ icon='VIEW_CAMERA'
+ ).mode = "DOLLY"
+
+ self.layout.operator(
OBJECT_OT_build_camera_rig.bl_idname,
text="Crane Camera Rig",
- icon='CAMERA_DATA'
- )
- op.mode = "CRANE"
+ icon='VIEW_CAMERA'
+ ).mode = "CRANE"
+
+ self.layout.operator(
+ OBJECT_OT_build_camera_rig.bl_idname,
+ text="2D Camera Rig",
+ icon='PIVOT_BOUNDBOX'
+ ).mode = "2D"
classes = (
diff --git a/add_camera_rigs/composition_guides_menu.py b/add_camera_rigs/composition_guides_menu.py
index c3ff43e5..16537525 100644
--- a/add_camera_rigs/composition_guides_menu.py
+++ b/add_camera_rigs/composition_guides_menu.py
@@ -19,7 +19,7 @@
import bpy
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):
bl_label = "Composition Guides"
@@ -29,7 +29,7 @@ class ADD_CAMERA_RIGS_PT_composition_guides(Panel):
def draw(self, context):
layout = self.layout
- arm, cam = get_arm_and_cam(context.active_object)
+ rig, cam = get_rig_and_cam(context.active_object)
cam = cam.data
layout.prop(cam, "show_safe_areas")
diff --git a/add_camera_rigs/create_widgets.py b/add_camera_rigs/create_widgets.py
index f19fc206..72a8c70d 100644
--- a/add_camera_rigs/create_widgets.py
+++ b/add_camera_rigs/create_widgets.py
@@ -1,4 +1,24 @@
+# ##### 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 mathutils import Vector
+from math import cos, sin, pi
def create_widget(name):
@@ -29,6 +49,44 @@ def create_widget(name):
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):
"""Create a compass-shaped widget"""
obj = create_widget(name)
diff --git a/add_camera_rigs/operators.py b/add_camera_rigs/operators.py
index 7468007e..058ed146 100644
--- a/add_camera_rigs/operators.py
+++ b/add_camera_rigs/operators.py
@@ -20,7 +20,7 @@ import bpy
from bpy.types import Operator
-def get_arm_and_cam(obj):
+def get_rig_and_cam(obj):
if obj.type == 'ARMATURE':
cam = None
for child in obj.children:
@@ -32,7 +32,8 @@ def get_arm_and_cam(obj):
elif (obj.type == 'CAMERA'
and obj.parent is not None
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 None, None
@@ -41,25 +42,31 @@ class CameraRigMixin():
@classmethod
def poll(cls, context):
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
-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_label = "Make Camera Active"
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):
- arm, cam = get_arm_and_cam(context.active_object)
+ rig, cam = get_rig_and_cam(context.active_object)
scene_cam = context.scene.camera
- if cam is not None and cam is not scene_cam:
- context.scene.camera = cam
- return {'FINISHED'}
-
- return {'CANCELLED'}
+ context.scene.camera = cam
+ return {'FINISHED'}
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)"
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(
"cam_" + str(context.scene.frame_current),
@@ -85,15 +92,15 @@ class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin):
bl_description = "Create Empty and add as DOF Object"
def execute(self, context):
- arm, cam = get_arm_and_cam(context.active_object)
- bone = arm.data.bones['Aim_shape_rotation-MCH']
+ rig, cam = get_rig_and_cam(context.active_object)
+ bone = rig.data.bones['Aim_shape_rotation-MCH']
# Add Empty
empty_obj = bpy.data.objects.new("EmptyDOF", None)
context.scene.collection.objects.link(empty_obj)
# Parent to Aim Child bone
- empty_obj.parent = arm
+ empty_obj.parent = rig
empty_obj.parent_type = "BONE"
empty_obj.parent_bone = "Aim_shape_rotation-MCH"
diff --git a/add_camera_rigs/ui_panels.py b/add_camera_rigs/ui_panels.py
index 63fe158a..0dc3c69e 100644
--- a/add_camera_rigs/ui_panels.py
+++ b/add_camera_rigs/ui_panels.py
@@ -19,7 +19,7 @@
import bpy
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):
@@ -30,66 +30,87 @@ class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin):
def draw(self, context):
active_object = context.active_object
- arm, cam = get_arm_and_cam(context.active_object)
- pose_bones = arm.pose.bones
+ rig, cam = get_rig_and_cam(context.active_object)
+ pose_bones = rig.pose.bones
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.dof, "use_dof")
- if cam_data.dof.use_dof:
- if cam_data.dof.focus_object is None:
- layout.operator("add_camera_rigs.add_dof_object",
- text="Add DOF Empty", icon="OUTLINER_OB_EMPTY")
- layout.prop(pose_bones["Camera"],
- '["focus_distance"]', text="Focus Distance")
- layout.prop(pose_bones["Camera"],
- '["aperture_fstop"]', text="F-Stop")
+ # DoF
+ col = layout.column(align=True)
+ col.prop(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:
+ col.operator("add_camera_rigs.add_dof_object",
+ text="Add DOF Empty", icon="OUTLINER_OB_EMPTY")
+ 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")
+ col.prop(pose_bones["Camera"],
+ '["aperture_fstop"]', text="F-Stop")
+
+ # Viewport display
layout.prop(active_object, 'show_in_front',
toggle=False, text='Show in Front')
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:
- layout.prop(cam_data, "passepartout_alpha")
+ col.prop(cam_data, "passepartout_alpha")
- layout.row().separator()
- # Added the comp guides here
+ # Composition guides
layout.popover(
panel="ADD_CAMERA_RIGS_PT_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")
-
- layout.operator("add_camera_rigs.add_marker_bind",
- text="Add Marker and Bind", icon="MARKER_HLT")
- if context.scene.camera is not cam:
- layout.operator("add_camera_rigs.set_scene_camera",
- text="Make Camera Active", icon='CAMERA_DATA')
-
- # Camera lens
- layout.separator()
- layout.prop(pose_bones["Camera"], '["lens"]', text="Focal Length (mm)")
-
- # Track to Constraint
- layout.label(text="Tracking:")
- layout.prop(pose_bones["Camera"].constraints["Track To"],
- 'influence', text="Aim Lock", slider=True)
-
- if arm["rig_id"].lower() == "crane_rig":
- col = layout.box().column()
+ col.operator("add_camera_rigs.add_marker_bind",
+ text="Add Marker and Bind", icon="MARKER_HLT")
+ col.operator("add_camera_rigs.set_scene_camera",
+ text="Make Camera Active", icon='CAMERA_DATA')
+
+ if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"):
+ # Track to Constraint
+ col = layout.column(align=True)
+ col.label(text="Tracking:")
+ col.prop(pose_bones["Camera"].constraints["Track To"],
+ 'influence', text="Aim Lock", slider=True)
# Crane arm stuff
- col.label(text="Crane Arm:")
- col.prop(pose_bones["Crane_height"],
- 'scale', index=1, text="Arm Height")
- col.prop(pose_bones["Crane_arm"],
- 'scale', index=1, text="Arm Length")
+ if rig["rig_id"].lower() == "crane_rig":
+ col = layout.column(align=True)
+ col.label(text="Crane Arm:")
+ col.prop(pose_bones["Crane_height"],
+ 'scale', index=1, text="Arm Height")
+ col.prop(pose_bones["Crane_arm"],
+ '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():