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
No related branches found
No related tags found
No related merge requests found
......@@ -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",
......
This diff is collapsed.
......@@ -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")
......
# ##### 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)
......
......@@ -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"
......
......@@ -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():
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment