2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,32 @@
# SPDX-FileCopyrightText: 2019 Wayne Dixon
#
# SPDX-License-Identifier: GPL-3.0-or-later
from . import build_rigs
from . import operators
from . import ui_panels
from . import prefs
from . import composition_guides_menu
# =========================================================================
# Registration:
# =========================================================================
def register():
build_rigs.register()
operators.register()
ui_panels.register()
prefs.register()
composition_guides_menu.register()
def unregister():
build_rigs.unregister()
operators.unregister()
ui_panels.unregister()
prefs.unregister()
composition_guides_menu.unregister()
if __name__ == "__main__":
register()
@@ -0,0 +1,30 @@
schema_version = "1.0.0"
id = "add_camera_rigs"
name = "Add Camera Rigs"
version = "1.8.1"
tagline = "Adds a Camera Rig with UI"
maintainer = "Community"
type = "add-on"
tags = ["Camera"]
blender_version_min = "4.2.0"
license = ["SPDX:GPL-3.0-or-later"]
website = "https://github.com/waylow/add_camera_rigs"
copyright = [
"2024 Wayne Dixon",
"2024 Brian Raschko",
"2024 Kris Wittig",
"2024 Damien Picard",
"2024 Flavio Perez",
]
[build]
paths_exclude_pattern = [
"/.git/",
"__pycache__/",
"images/",
".*",
"*.blend",
"*.blend[0123456789]",
"*.md",
"*.zip",
]
@@ -0,0 +1,796 @@
# SPDX-FileCopyrightText: 2019 Wayne Dixon
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from bpy.types import Operator
from bpy_extras import object_utils
from mathutils import Vector
from math import pi
from .create_widgets import (
create_root_widget,
create_camera_widget,
create_aim_widget,
create_corner_widget,
create_circle_widget,
create_star_widget,
create_cross_widget,
create_2d_root_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 = prop_from
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 = prop_from
return driver
def create_dolly_bones(rig):
"""Create bones for the dolly camera rig"""
bones = rig.data.edit_bones
# Add bone collections
collection_controls = rig.data.collections.new(name="Controls")
collection_mch = rig.data.collections.new(name="MCH")
collection_mch.is_visible = False
# Add new bones
root = bones.new("Root")
root.tail = (0.0, 1.0, 0.0)
root.show_wire = True
root.color.palette = 'THEME02'
collection_controls.assign(root)
ctrl_aim_child = bones.new("MCH-Aim_shape_rotation")
ctrl_aim_child.head = (0.0, 10.0, 1.7)
ctrl_aim_child.tail = (0.0, 11.0, 1.7)
collection_mch.assign(ctrl_aim_child)
ctrl_aim = bones.new("Aim")
ctrl_aim.head = (0.0, 10.0, 1.7)
ctrl_aim.tail = (0.0, 11.0, 1.7)
ctrl_aim.show_wire = True
ctrl_aim.color.palette = 'THEME04'
collection_controls.assign(ctrl_aim)
ctrl = bones.new("Camera")
ctrl.head = (0.0, 0.0, 1.7)
ctrl.tail = (0.0, 1.0, 1.7)
ctrl.show_wire = True
ctrl.color.palette = 'THEME02'
collection_controls.assign(ctrl)
ctrl_offset = bones.new("Camera_Offset")
ctrl_offset.head = (0.0, 0.0, 1.7)
ctrl_offset.tail = (0.0, 1.0, 1.7)
ctrl_offset.show_wire = True
ctrl_offset.color.palette = 'THEME09'
collection_controls.assign(ctrl_offset)
# Setup hierarchy
ctrl.parent = root
ctrl_offset.parent = ctrl
ctrl_aim.parent = root
ctrl_aim_child.parent = ctrl_aim
# Jump into object mode
bpy.ops.object.mode_set(mode='OBJECT')
pose_bones = rig.pose.bones
# Lock the relevant scale channels of the Camera_offset bone
pose_bones["Camera_Offset"].lock_scale = (True,) * 3
def create_crane_bones(rig):
"""Create bones for the crane camera rig"""
bones = rig.data.edit_bones
# Add bone collections
collection_controls = rig.data.collections.new(name="Controls")
collection_mch = rig.data.collections.new(name="MCH")
collection_mch.is_visible = False
# Add new bones
root = bones.new("Root")
root.tail = (0.0, 1.0, 0.0)
root.show_wire = True
root.color.palette = 'THEME02'
collection_controls.assign(root)
ctrl_aim_child = bones.new("MCH-Aim_shape_rotation")
ctrl_aim_child.head = (0.0, 10.0, 1.7)
ctrl_aim_child.tail = (0.0, 11.0, 1.7)
collection_mch.assign(ctrl_aim_child)
ctrl_aim = bones.new("Aim")
ctrl_aim.head = (0.0, 10.0, 1.7)
ctrl_aim.tail = (0.0, 11.0, 1.7)
ctrl_aim.show_wire = True
ctrl_aim.color.palette = 'THEME04'
collection_controls.assign(ctrl_aim)
ctrl = bones.new("Camera")
ctrl.head = (0.0, 1.0, 1.7)
ctrl.tail = (0.0, 2.0, 1.7)
ctrl.color.palette = 'THEME02'
collection_controls.assign(ctrl)
ctrl_offset = bones.new("Camera_Offset")
ctrl_offset.head = (0.0, 1.0, 1.7)
ctrl_offset.tail = (0.0, 2.0, 1.7)
ctrl_offset.color.palette = 'THEME09'
collection_controls.assign(ctrl_offset)
arm = bones.new("Crane_Arm")
arm.head = (0.0, 0.0, 1.7)
arm.tail = (0.0, 1.0, 1.7)
arm.bbone_x = 0.05
arm.bbone_z = 0.05
arm.color.palette = 'THEME07'
collection_controls.assign(arm)
height = bones.new("Crane_Height")
height.head = (0.0, 0.0, 0.0)
height.tail = (0.0, 0.0, 1.7)
height.bbone_x = 0.05
height.bbone_z = 0.05
height.color.palette = 'THEME07'
collection_controls.assign(height)
# Setup hierarchy
ctrl.parent = arm
ctrl_offset.parent = ctrl
ctrl.use_inherit_rotation = False
ctrl.inherit_scale = "NONE"
ctrl.show_wire = True
arm.parent = height
arm.inherit_scale = "NONE"
height.parent = root
ctrl_aim.parent = root
ctrl_aim_child.parent = ctrl_aim
# Jump into object mode
bpy.ops.object.mode_set(mode='OBJECT')
pose_bones = rig.pose.bones
# Lock the relevant loc, rot and scale
pose_bones["Crane_Arm"].lock_rotation = (False, True, False)
pose_bones["Crane_Arm"].lock_scale = (True, False, True)
pose_bones["Crane_Height"].lock_location = (True,) * 3
pose_bones["Crane_Height"].lock_rotation = (True,) * 3
pose_bones["Crane_Height"].lock_scale = (True, False, True)
pose_bones["Camera_Offset"].lock_scale = (True,) * 3
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 bone in pose_bones:
bone.rotation_mode = 'XYZ'
# Lens property
pb = pose_bones['Camera']
pb["lens"] = 50.0
ui_data = pb.id_properties_ui("lens")
ui_data.update(min=1.0, max=1000000.0, soft_max=5000.0, default=50.0, subtype="DISTANCE_CAMERA")
# lens offset property
pb = pose_bones['Camera']
pb["lens_offset"] = 0.0
ui_data = pb.id_properties_ui("lens_offset")
ui_data.update(min=-1000000.0, max=1000000.0, soft_max = 5000.0, soft_min = -5000.0,default=0.0)
# Build the widgets
root_widget = create_root_widget("Camera_Root")
camera_widget = create_camera_widget("Camera")
camera_offset_widget = create_circle_widget("Camera_Offset", radius=0.23)
aim_widget = create_aim_widget("Aim")
# Add the custom bone shapes
pose_bones["Root"].custom_shape = root_widget
pose_bones["Aim"].custom_shape = aim_widget
pose_bones["Camera"].custom_shape = camera_widget
pose_bones["Camera_Offset"].custom_shape = camera_offset_widget
# Set the "Override Transform" field to the mechanism position
pose_bones["Aim"].custom_shape_transform = pose_bones["MCH-Aim_shape_rotation"]
# Add constraints to bones
con = pose_bones['MCH-Aim_shape_rotation'].constraints.new('COPY_ROTATION')
con.target = rig
con.subtarget = "Camera"
con = pose_bones['Camera'].constraints.new('TRACK_TO')
con.track_axis = 'TRACK_Y'
con.up_axis = 'UP_Z'
con.target = rig
con.subtarget = "Aim"
con.use_target_z = True
cam.data.display_size = 1.0
cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x
drv = create_prop_driver(rig, cam, "lens", "lens")
# create driver variables (for Dolly Zoom switching)
var = drv.driver.variables.new()
var.name = 'lens_offset'
var.type = 'SINGLE_PROP'
var.targets[0].id = rig
var.targets[0].data_path = 'pose.bones["Camera"]["lens_offset"]'
var = drv.driver.variables.new()
var.name = 'distance'
var.type = 'LOC_DIFF'
var.targets[0].id = rig
var.targets[0].bone_target = 'Camera'
var.targets[1].id = rig
var.targets[1].bone_target = 'Aim'
var = drv.driver.variables.new()
var.name = 'root_scale'
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].transform_type = 'SCALE_AVG'
var.targets[0].bone_target = 'Root'
def create_2d_bones(rig, cam):
"""Create bones for the 2D camera rig"""
bones = rig.data.edit_bones
# Add bone collections
collection_controls = rig.data.collections.new(name="Controls")
collection_offsets = rig.data.collections.new(name="Offsets")
collection_extras = rig.data.collections.new(name="Extras")
collection_mch = rig.data.collections.new(name="MCH")
collection_mch.is_visible = False
# Add new bones
bones = rig.data.edit_bones
root = bones.new("Root")
root.tail = (0.0, 0.0, 1.0)
root.show_wire = True
root.color.palette = 'THEME02'
collection_controls.assign(root)
ctrl_offset = bones.new("Root_Offset")
ctrl_offset.head = (0.0, 0.0, 0.0)
ctrl_offset.tail = (0.0, 0.0, 1.0)
ctrl_offset.show_wire = True
ctrl_offset.color.palette = 'THEME04'
collection_offsets.assign(ctrl_offset)
ctrl_tweak = bones.new("Root_Tweak")
ctrl_tweak.head = (0.0, 0.0, 0.0)
ctrl_tweak.tail = (0.0, 0.0, 1.0)
ctrl_tweak.show_wire = True
ctrl_tweak.color.palette = 'THEME09'
collection_extras.assign(ctrl_tweak)
ctrl_camera = bones.new("Camera")
ctrl_camera.head = (0.0, 0.0, 0.0)
ctrl_camera.tail = (0.0, 0.0, 1.0)
ctrl_camera.show_wire = True
ctrl_camera.color.palette = 'THEME02'
collection_controls.assign(ctrl_camera)
ctrl_aim = bones.new("Aim")
ctrl_aim.head = (0.0, 10.0, 0.0)
ctrl_aim.tail = (0.0, 10.0, 1.0)
ctrl_aim.show_wire = True
ctrl_aim.color.palette = 'THEME04'
collection_offsets.assign(ctrl_aim)
left_corner = bones.new("Left_Corner")
left_corner.head = (-3.0, 10.0, -1.7)
left_corner.tail = left_corner.head + Vector((0.0, 0.0, 1.0))
left_corner.show_wire = True
left_corner.color.palette = 'THEME02'
collection_controls.assign(left_corner)
right_corner = bones.new("Right_Corner")
right_corner.head = (3.0, 10.0, -1.7)
right_corner.tail = right_corner.head + Vector((0.0, 0.0, 1.0))
right_corner.show_wire = True
right_corner.color.palette = 'THEME02'
collection_controls.assign(right_corner)
corner_distance_x = (left_corner.head - right_corner.head).length
corner_distance_y = ctrl_camera.head.z - left_corner.head.z
corner_distance_z = left_corner.head.y
collection_controls.assign(root)
center = bones.new("MCH-Center")
center.head = ((right_corner.head + left_corner.head) / 2.0)
center.tail = center.head + Vector((0.0, 0.0, 1.0))
center.show_wire = True
collection_mch.assign(center)
dof = bones.new("DOF")
dof.head = ctrl_aim.head
dof.tail = ctrl_aim.tail
dof.show_wire = True
dof.color.palette = 'THEME09'
collection_extras.assign(dof)
dof_parent = bones.new("MCH-DOF_Parent")
dof_parent.head = ctrl_aim.head
dof_parent.tail = ctrl_aim.tail
dof_parent.show_wire = True
collection_mch.assign(dof_parent)
# Setup hierarchy
ctrl_offset.parent = root
ctrl_tweak.parent = ctrl_offset
ctrl_camera.parent = ctrl_tweak
ctrl_aim.parent = ctrl_tweak
left_corner.parent = ctrl_aim
right_corner.parent = ctrl_aim
center.parent = ctrl_tweak
dof_parent.parent = root
dof.parent = dof_parent
# 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["MCH-Center"].driver_add("location")
# Center X driver
driver = center_drivers[0].driver
driver.expression = "aim + (left + right) / 2.0"
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'
var = driver.variables.new()
var.name = "aim"
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Aim"
var.targets[0].transform_type = 'LOC_X'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
# Center Y driver
driver = center_drivers[1].driver
driver.expression = "({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + aim_y + (left_y + right_y)/2".format(
distance_x=corner_distance_x)
for corner in ("left", "right"):
for direction in ("x", "y"):
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'
for direction in ("x", "y"):
var = driver.variables.new()
var.name = "aim_%s" % direction
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Aim"
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 = 'CONTEXT_PROP'
var.targets[0].context_property = 'ACTIVE_SCENE'
var.targets[0].data_path = "render.resolution_" + direction
# Center Z driver
driver = center_drivers[2].driver
driver.expression = "aim + (left + right) / 2.0"
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'
var = driver.variables.new()
var.name = "aim"
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Aim"
var.targets[0].transform_type = 'LOC_Z'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
# Bone constraints
center_con = pose_bones['Camera'].constraints.new('DAMPED_TRACK')
center_con.target = rig
center_con.subtarget = "MCH-Center"
center_con.track_axis = 'TRACK_NEGATIVE_Z'
dof_con = pose_bones["MCH-DOF_Parent"].constraints.new('COPY_LOCATION')
dof_con.target = rig
dof_con.subtarget = "Aim"
dof_con.target_space = 'POSE'
dof_con.owner_space = 'POSE'
# Build the widgets
root_widget = create_2d_root_widget("Camera_2D_Root")
root_offset_widget = create_circle_widget("Camera_2D_Offset", radius=0.68)
root_tweak_widget = create_star_widget("Camera_2D_Tweak", radius=0.53)
camera_widget = create_camera_widget("Camera_2D", scale=0.5)
aim_widget = create_aim_widget("Camera_2D_Aim", inner_circle=False)
left_widget = create_corner_widget("Left_Corner", reverse=True)
right_widget = create_corner_widget("Right_Corner")
dof_widget = create_cross_widget("Camera_2D_DOF")
# Add the custom bone shapes
pose_bones["Root"].custom_shape = root_widget
pose_bones["Root_Offset"].custom_shape = root_offset_widget
pose_bones["Root_Tweak"].custom_shape = root_tweak_widget
pose_bones["Camera"].custom_shape = camera_widget
pose_bones["Aim"].custom_shape = aim_widget
pose_bones["Left_Corner"].custom_shape = left_widget
pose_bones["Right_Corner"].custom_shape = right_widget
pose_bones["DOF"].custom_shape = dof_widget
# Set bone shape transforms
pose_bones["Root_Offset"].custom_shape_rotation_euler.x = pi / 2.0
pose_bones["Camera"].custom_shape_rotation_euler.x = pi / 2.0
pose_bones["Aim"].custom_shape_rotation_euler.x = pi / 2.0
# Lock the relevant loc, rot and scale
pose_bones["Root"].lock_scale = (True,) * 3
pose_bones["Root_Offset"].lock_scale = (True,) * 3
pose_bones["Root_Tweak"].lock_scale = (True,) * 3
pose_bones["Camera"].lock_rotation = (True,) * 3
pose_bones["Camera"].lock_scale = (True,) * 3
pose_bones["Aim"].lock_rotation = (True,) * 3
pose_bones["Aim"].lock_scale = (True,) * 3
pose_bones["Left_Corner"].lock_rotation = (True,) * 3
pose_bones["Right_Corner"].lock_rotation = (True,) * 3
# Camera settings
cam.data.sensor_fit = "HORIZONTAL" # Avoids distortion in portrait format
cam.data.dof.focus_object = rig
cam.data.dof.focus_subtarget = "DOF"
# Property to switch between rotation and switch mode
pose_bones["Camera"]["rotation_shift"] = 0.0
ui_data = pose_bones["Camera"].id_properties_ui("rotation_shift")
ui_data.update(min=0.0, max=1.0, description="rotation_shift")
# Rotation / shift switch driver
driver = center_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 - aim_z + cam_z) * 36 / (frame_width or 1.0)".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'
var = driver.variables.new()
var.name = "aim_z"
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Aim"
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 + aim_x - cam_x) / (frame_width or 1.0))"
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"]'
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 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 = "aim_x"
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Aim"
var.targets[0].transform_type = 'LOC_X'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = "cam_x"
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Camera"
var.targets[0].transform_type = "LOC_X"
var.targets[0].transform_space = 'TRANSFORM_SPACE'
# Shift driver Y
driver = cam.data.driver_add('shift_y').driver
driver.expression = (
"rotation_shift * -("
"({distance_y} - (left_y + right_y)/2 - aim_y + cam_y)"
" / (frame_width or 1.0) - (res_y/res_x)/2)"
).format(distance_y=corner_distance_y)
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"]'
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 = "%s_y" % corner
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = corner.capitalize() + "_Corner"
var.targets[0].transform_type = 'LOC_Y'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = "aim_y"
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Aim"
var.targets[0].transform_type = 'LOC_Y'
var.targets[0].transform_space = 'TRANSFORM_SPACE'
var = driver.variables.new()
var.name = "cam_y"
var.type = 'TRANSFORMS'
var.targets[0].id = rig
var.targets[0].bone_target = "Camera"
var.targets[0].transform_type = 'LOC_Y'
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.type = 'CONTEXT_PROP'
var.targets[0].context_property = 'ACTIVE_SCENE'
var.targets[0].data_path = 'render.resolution_' + direction
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)
context.scene.camera = cam
# 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"] = 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(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"
if mode == "2D":
cam.parent_bone = "Camera"
else:
cam.parent_bone = "Camera_Offset"
# 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 armatures 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
ui_data = pb.id_properties_ui('focus_distance')
ui_data.update(min=0.0, default=10.0)
# DOF F-Stop property
pb = pose_bones['Camera']
pb["aperture_fstop"] = 2.8
ui_data = pb.id_properties_ui('aperture_fstop')
ui_data.update(min=0.0, soft_min=0.1, soft_max=128.0, default=2.8)
# Add drivers to link the camera properties to the custom props
# on the armature
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
view_layer = context.view_layer
for obj in view_layer.objects:
obj.select_set(False)
rig.select_set(True)
view_layer.objects.active = rig
class OBJECT_OT_build_camera_rig(Operator):
bl_idname = "object.build_camera_rig"
bl_label = "Build Camera Rig"
bl_description = "Build a Camera Rig"
bl_options = {'REGISTER', 'UNDO'}
mode: bpy.props.EnumProperty(items=(('DOLLY', 'Dolly', 'Dolly rig'),
('CRANE', 'Crane', 'Crane rig',),
('2D', '2D', '2D rig')),
name="mode",
description="Type of camera to create",
default="DOLLY")
def execute(self, context):
# Build the rig
build_camera_rig(context, self.mode)
return {'FINISHED'}
def add_dolly_crane_buttons(self, context):
"""Dolly and crane entries in the Add Object > Camera Menu"""
if context.mode == 'OBJECT':
self.layout.operator(
OBJECT_OT_build_camera_rig.bl_idname,
text="Dolly Camera Rig",
icon='VIEW_CAMERA'
).mode = "DOLLY"
self.layout.operator(
OBJECT_OT_build_camera_rig.bl_idname,
text="Crane Camera Rig",
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 = (
OBJECT_OT_build_camera_rig,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
bpy.types.VIEW3D_MT_camera_add.append(add_dolly_crane_buttons)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
bpy.types.VIEW3D_MT_camera_add.remove(add_dolly_crane_buttons)
if __name__ == "__main__":
register()
@@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: 2019 Wayne Dixon
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from bpy.types import Panel
from .operators import get_rig_and_cam
class ADD_CAMERA_RIGS_PT_composition_guides(Panel):
bl_label = "Composition Guides"
bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
def draw(self, context):
layout = self.layout
rig, cam = get_rig_and_cam(context.active_object)
cam = cam.data
layout.prop(cam, "show_safe_areas")
layout.row().separator()
layout.prop(cam, "show_composition_center")
layout.prop(cam, "show_composition_center_diagonal")
layout.prop(cam, "show_composition_golden")
layout.prop(cam, "show_composition_golden_tria_a")
layout.prop(cam, "show_composition_golden_tria_b")
layout.prop(cam, "show_composition_harmony_tri_a")
layout.prop(cam, "show_composition_harmony_tri_b")
layout.prop(cam, "show_composition_thirds")
def register():
bpy.utils.register_class(ADD_CAMERA_RIGS_PT_composition_guides)
def unregister():
bpy.utils.unregister_class(ADD_CAMERA_RIGS_PT_composition_guides)
if __name__ == "__main__":
register()
@@ -0,0 +1,328 @@
# SPDX-FileCopyrightText: 2019 Wayne Dixon
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from mathutils import Vector
from math import cos, sin, pi
def create_widget(name):
"""Create an empty widget object and return the object"""
prefs = bpy.context.preferences.addons[__package__].preferences
widget_prefix = prefs.widget_prefix
obj_name = widget_prefix + name
scene = bpy.context.scene
obj = bpy.data.objects.get(obj_name)
if obj is None:
mesh = bpy.data.meshes.new(obj_name)
obj = bpy.data.objects.new(obj_name, mesh)
# Create a new collection for the widgets
collection_name = prefs.camera_widget_collection_name
coll = bpy.data.collections.get(collection_name)
if coll is None:
coll = bpy.data.collections.new(collection_name)
scene.collection.children.link(coll)
coll.hide_viewport = True
coll.hide_render = True
# Link the collection
coll.objects.link(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,
0.0,
sin(angle) * radius,
)))
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_star_widget(name, radius=1.0):
"""Create a star-shaped widget"""
obj = create_widget(name)
if not obj.data.vertices:
vert_n = 32
verts = []
for n in range(vert_n):
angle = n / vert_n * 2*pi
loc = Vector((cos(angle) * radius, sin(angle) * radius, 0.0))
if n % 2:
loc.length = radius * 0.92
verts.append(loc)
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_cross_widget(name, width=0.1, length=0.82, scale=0.35):
"""Create a cross-shaped widget"""
obj = create_widget(name)
if not obj.data.vertices:
verts = (
scale * Vector((width, width, 0.0)),
scale * Vector((length, width, 0.0)),
scale * Vector((length, -width, 0.0)),
scale * Vector((width, -width, 0.0)),
scale * Vector((width, -length, 0.0)),
scale * Vector((-width, -length, 0.0)),
scale * Vector((-width, -width, 0.0)),
scale * Vector((-length, -width, 0.0)),
scale * Vector((-length, width, 0.0)),
scale * Vector((-width, width, 0.0)),
scale * Vector((-width, length, 0.0)),
scale * Vector((width, length, 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)
if not obj.data.vertices:
verts = [(0.636, 0.636, 0.0),
(0.344, 0.831, 0.0),
(0.0, 0.9, 0.0),
(-0.344, 0.831, 0.0),
(-0.636, 0.636, 0.0),
(-0.831, 0.344, 0.0),
(-0.9, 0.0, 0.0),
(0.9, 0.0, 0.0),
(0.831, 0.344, 0.0),
(0.2, 1.52, 0.0),
(-0.2, 1.52, 0.0),
(-0.2, 1.15, 0.0),
(0.2, 1.15, 0.0),
(-0.4, 1.52, 0.0),
(0.4, 1.52, 0.0),
(0.0, 2.0, 0.0),
(-0.831, -0.344, 0.0),
(0.831, -0.344, 0.0),
(0.636, -0.636, 0.0),
(0.344, -0.831, 0.0),
(0.0, -0.9, 0.0),
(-0.344, -0.831, 0.0),
(-0.636, -0.636, 0.0),
(-2.0, 0.0, 0.0),
(-1.52, 0.4, 0.0),
(-1.52, -0.4, 0.0),
(-1.15, 0.2, 0.0),
(-1.15, -0.2, 0.0),
(-1.52, -0.2, 0.0),
(-1.52, 0.2, 0.0),
(1.52, -0.2, 0.0),
(1.52, 0.2, 0.0),
(1.15, 0.2, 0.0),
(1.15, -0.2, 0.0),
(1.52, 0.4, 0.0),
(1.52, -0.4, 0.0),
(2.0, 0.0, 0.0),
(0.0, -2.0, 0.0),
(-0.4, -1.52, 0.0),
(0.4, -1.52, 0.0),
(-0.2, -1.15, 0.0),
(0.2, -1.15, 0.0),
(0.2, -1.52, 0.0),
(-0.2, -1.52, 0.0)]
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (7, 8), (0, 8),
(10, 11), (9, 12), (11, 12), (10, 13), (9, 14), (13, 15), (14, 15),
(16, 22), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (7, 17),
(6, 16), (23, 24), (23, 25), (24, 29), (25, 28), (26, 27), (26, 29),
(27, 28), (31, 32), (30, 33), (32, 33), (31, 34), (30, 35), (34, 36),
(35, 36), (37, 38), (37, 39), (38, 43), (39, 42), (40, 41), (40, 43),
(41, 42)]
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
return obj
def create_camera_widget(name, scale=1.0):
"""Create a camera control widget"""
obj = create_widget(name)
if not obj.data.vertices:
verts = [scale * Vector((0.275, 0.0, -0.275)),
scale * Vector((0.360, 0.0, -0.149)),
scale * Vector((0.389, 0.0, 0.0)),
scale * Vector((0.360, 0.0, 0.149)),
scale * Vector((0.275, 0.0, 0.275)),
scale * Vector((0.149, 0.0, 0.360)),
scale * Vector((0.0, 0.0, 0.389)),
scale * Vector((0.0, 0.0, -0.389)),
scale * Vector((0.149, 0.0, -0.360)),
scale * Vector((0.663, 0.0, -0.093)),
scale * Vector((0.663, 0.0, 0.093)),
scale * Vector((0.497, 0.0, 0.093)),
scale * Vector((0.497, 0.0, -0.093)),
scale * Vector((0.663, 0.0, 0.173)),
scale * Vector((0.663, 0.0, -0.173)),
scale * Vector((0.875, 0.0, 0.0)),
scale * Vector((-0.149, 0.0, 0.360)),
scale * Vector((-0.149, 0.0, -0.360)),
scale * Vector((-0.275, 0.0, -0.275)),
scale * Vector((-0.360, 0.0, -0.149)),
scale * Vector((-0.389, 0.0, 0.0)),
scale * Vector((-0.360, 0.0, 0.149)),
scale * Vector((-0.275, 0.0, 0.275)),
scale * Vector((0.0, 0.0, 0.875)),
scale * Vector((0.173, 0.0, 0.663)),
scale * Vector((-0.173, 0.0, 0.663)),
scale * Vector((0.093, 0.0, 0.497)),
scale * Vector((-0.093, 0.0, 0.497)),
scale * Vector((-0.093, 0.0, 0.663)),
scale * Vector((0.093, 0.0, 0.663)),
scale * Vector((-0.093, 0.0, -0.663)),
scale * Vector((0.093, 0.0, -0.663)),
scale * Vector((0.093, 0.0, -0.497)),
scale * Vector((-0.093, 0.0, -0.497)),
scale * Vector((0.173, 0.0, -0.663)),
scale * Vector((-0.173, 0.0, -0.663)),
scale * Vector((0.0, 0.0, -0.875)),
scale * Vector((-0.875, 0.0, 0.0)),
scale * Vector((-0.663, 0.0, 0.173)),
scale * Vector((-0.663, 0.0, -0.173)),
scale * Vector((-0.497, 0.0, 0.093)),
scale * Vector((-0.497, 0.0, -0.093)),
scale * Vector((-0.663, 0.0, -0.093)),
scale * Vector((-0.663, 0.0, 0.093))]
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (7, 8), (0, 8),
(10, 11), (9, 12), (11, 12), (10, 13), (9, 14), (13, 15), (14, 15),
(16, 22), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (7, 17),
(6, 16), (23, 24), (23, 25), (24, 29), (25, 28), (26, 29), (27, 28),
(31, 32), (30, 33), (32, 33), (31, 34), (30, 35), (34, 36), (35, 36),
(37, 38), (37, 39), (38, 43), (39, 42), (40, 41), (40, 43), (41, 42),
(27, 26)]
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
return obj
def create_aim_widget(name, inner_circle=True):
"""Create a camera aim widget"""
obj = create_widget(name)
if not obj.data.vertices:
verts = [(0.311, 0.0, 0.311), (0.406, 0.0, 0.168), (0.44, 0.0, 0.0),
(0.406, 0.0, -0.168), (0.311, 0.0, -0.311),
(0.168, 0.0, -0.406), (0.0, 0.0, -0.44), (0.0, 0.0, 0.44),
(0.168, 0.0, 0.406), (0.8, 0.0, 0.1), (0.8, 0.0, -0.1),
(1.0, 0.0, -0.1), (1.0, 0.0, 0.1), (0.8, 0.0, -0.2),
(0.8, 0.0, 0.2), (0.56, 0.0, 0.0), (-0.168, 0.0, -0.406),
(-0.168, 0.0, 0.406), (-0.311, 0.0, 0.311),
(-0.406, 0.0, 0.168), (-0.44, 0.0, 0.0),
(-0.406, 0.0, -0.168), (-0.311, 0.0, -0.311),
(0.0, 0.0, 0.56), (-0.2, 0.0, 0.8), (0.2, 0.0, 0.8),
(-0.1, 0.0, 1.0), (0.1, 0.0, 1.0), (0.1, 0.0, 0.8),
(-0.1, 0.0, 0.8), (0.1, 0.0, -0.8), (-0.1, 0.0, -0.8),
(-0.1, 0.0, -1.0), (0.1, 0.0, -1.0), (-0.2, 0.0, -0.8),
(0.2, 0.0, -0.8), (0.0, 0.0, -0.56), (-0.56, 0.0, 0.0),
(-0.8, 0.0, -0.2), (-0.8, 0.0, 0.2), (-1.0, 0.0, -0.1),
(-1.0, 0.0, 0.1), (-0.8, 0.0, 0.1), (-0.8, 0.0, -0.1),
(0.0, 0.0, 0.06), (0.0, 0.0, -0.06), (-0.06, 0.0, 0.0),
(0.06, 0.0, 0.0)]
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (7, 8),
(0, 8), (9, 12), (10, 11), (11, 12), (10, 13), (9, 14),
(13, 15), (14, 15), (16, 22), (17, 18), (18, 19), (19, 20),
(20, 21), (21, 22), (7, 17), (6, 16), (23, 24), (23, 25),
(24, 29), (25, 28), (26, 29), (26, 27), (27, 28), (31, 32),
(30, 33), (32, 33), (31, 34), (30, 35), (34, 36), (35, 36),
(37, 38), (37, 39), (38, 43), (39, 42), (40, 41), (40, 43),
(41, 42), (44, 45), (46, 47)]
if inner_circle:
verts.extend((
(-0.127, 0.0, -0.127), (-0.166, 0.0, -0.068),
(-0.18, 0.0, 0.0), (-0.166, 0.0, 0.068),
(-0.127, 0.0, 0.127), (-0.068, 0.0, 0.166),
(-0.068, 0.0, -0.166), (0.068, 0.0, 0.166),
(0.0, 0.0, 0.18), (0.0, 0.0, -0.18),
(0.068, 0.0, -0.166), (0.127, 0.0, -0.127),
(0.166, 0.0, -0.068), (0.18, 0.0, 0.0),
(0.166, 0.0, 0.068), (0.127, 0.0, 0.127),
))
edges.extend((
(54, 57), (53, 56), (48, 49), (49, 50), (50, 51), (51, 52),
(52, 53), (48, 54), (55, 63), (55, 56), (57, 58), (58, 59),
(59, 60), (60, 61), (61, 62), (62, 63)))
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
return obj
# 2D rig widgets
def create_2d_root_widget(name):
"""Create a 2D camera root widget"""
obj = create_widget(name)
if not obj.data.vertices:
verts = (
(-0.685, 0.685, 0.0), (-0.371, 0.742, 0.0), (-0.382, 0.69, 0.0),
(-0.636, 0.636, 0.0), (-0.69, 0.382, 0.0), (-0.742, 0.371, 0.0),
(0.688, 0.685, 0.0), (0.742, 0.371, 0.0), (0.69, 0.382, 0.0),
(0.636, 0.636, 0.0), (0.382, 0.69, 0.0), (0.371, 0.742, 0.0),
(0.688, -0.685, 0.0), (0.371, -0.742, 0.0), (0.382, -0.69, 0.0),
(0.636, -0.636, 0.0), (0.69, -0.382, 0.0), (0.742, -0.371, 0.0),
(-0.685, -0.685, 0.0), (-0.742, -0.371, 0.0), (-0.69, -0.382, 0.0),
(-0.636, -0.636, 0.0), (-0.382, -0.69, 0.0), (-0.371, -0.742, 0.0),
)
edges = (
(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0), (6, 7), (7, 8),
(8, 9), (9, 10), (10, 11), (11, 6), (12, 13), (13, 14), (14, 15),
(15, 16), (16, 17), (17, 12), (18, 19), (19, 20), (20, 21),
(21, 22), (22, 23), (23, 18),
)
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
return obj
@@ -0,0 +1,277 @@
# SPDX-FileCopyrightText: 2019 Wayne Dixon
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
import mathutils
from bpy.types import Operator
def get_rig_and_cam(obj):
if (obj.type == 'ARMATURE'
and "rig_id" in obj
and obj["rig_id"].lower() in {"dolly_rig",
"crane_rig", "2d_rig"}):
cam = None
for child in obj.children:
if child.type == 'CAMERA':
cam = child
break
if cam is not None:
return obj, cam
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", "2d_rig"}):
return obj.parent, obj
return None, None
def calculate_aim_distance(obj):
'''This will return the distance of the camera and the aim bone at the time it is called.'''
camera_bone = obj.pose.bones['Camera'].matrix
aim_bone = obj.pose.bones['Aim'].matrix
length = (camera_bone - aim_bone).to_translation().length
return length
def poll_base(cls, context):
if context.active_object is None:
if hasattr(cls, "poll_message_set"):
cls.poll_message_set("No object is selected.")
return False
if None in get_rig_and_cam(context.active_object):
if hasattr(cls, "poll_message_set"):
cls.poll_message_set("Active object is not in a camera rig.")
return False
return True
def poll_perspective(cls, context):
if not poll_base(cls, context):
return False
rig, cam = get_rig_and_cam(context.active_object)
if cam.data.type == 'ORTHO':
cls.poll_message_set("This operator is not supported for orthographic cameras.")
return False
if rig["rig_id"].lower() == '2d_rig':
cls.poll_message_set("This operator is not supported for 2D camera rigs.")
return False
return True
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 not poll_base(cls, context):
return False
_rig, cam = get_rig_and_cam(context.active_object)
if cam is context.scene.camera:
cls.poll_message_set("Selected camera is already the scene camera.")
return False
return True
def execute(self, context):
rig, cam = get_rig_and_cam(context.active_object)
context.scene.camera = cam
return {'FINISHED'}
class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator):
bl_idname = "add_camera_rigs.add_marker_bind"
bl_label = "Add Marker and Bind Camera"
bl_description = "Add marker to current frame then bind rig camera to it (for camera switching)"
@classmethod
def poll(cls, context):
return poll_base(cls, context)
def execute(self, context):
rig, cam = get_rig_and_cam(context.active_object)
marker = context.scene.timeline_markers.new(
"cam_" + str(context.scene.frame_current),
frame=context.scene.frame_current
)
marker.camera = cam
return {'FINISHED'}
class ADD_CAMERA_RIGS_OT_set_dof_bone(Operator):
bl_idname = "add_camera_rigs.set_dof_bone"
bl_label = "Set DOF to Aim"
bl_description = "Set the Aim bone as a DOF target"
@classmethod
def poll(cls, context):
return poll_base(cls, context)
def execute(self, context):
rig, cam = get_rig_and_cam(context.active_object)
cam.data.dof.focus_object = rig
cam.data.dof.focus_subtarget = (
'DOF' if rig["rig_id"].lower() == '2d_rig'
else 'MCH-Aim_shape_rotation')
return {'FINISHED'}
class ADD_CAMERA_RIGS_OT_set_dolly_zoom(Operator):
bl_idname = "add_camera_rigs.set_dolly_zoom"
bl_label = "Set Dolly Zoom"
bl_description = "Use the aim bone as a focal length (Dolly Zoom effect)"
@classmethod
def poll(cls, context):
return poll_perspective(cls, context)
def execute(self, context):
rig, cam = get_rig_and_cam(context.active_object)
value = calculate_aim_distance(rig)
drv = cam.data.animation_data.drivers[0]
drv.driver.expression = '(distance * (lens+lens_offset) / %s ) / root_scale' % value
# set the bone color to default
rig.pose.bones["Aim"].color.palette = 'THEME01'
return {'FINISHED'}
class ADD_CAMERA_RIGS_OT_remove_dolly_zoom(Operator):
bl_idname = "add_camera_rigs.remove_dolly_zoom"
bl_label = "Remove Dolly Zoom"
bl_description = "Disconnect the aim bone as a focal length (Dolly Zoom effect)"
@classmethod
def poll(cls, context):
return poll_perspective(cls, context)
def execute(self, context):
rig, cam = get_rig_and_cam(context.active_object)
lens_value = cam.data.lens
# set the lens to the current value
drv = cam.data.animation_data.drivers[0]
drv.driver.expression = 'lens'
rig.pose.bones["Camera"]["lens"] = lens_value
# reset the offset back to zero
rig.pose.bones["Camera"]["lens_offset"] = 0.0
#set the bone color to default
rig.pose.bones["Aim"].color.palette = 'DEFAULT'
return {'FINISHED'}
class ADD_CAMERA_RIGS_OT_shift_to_pivot(Operator):
bl_idname = "add_camera_rigs.shift_to_pivot"
bl_label = "Shift To Pivot"
bl_description = "Offset the Camera and Aim such that the Aim bone is above the Root control"
@classmethod
def poll(cls, context):
return poll_base(cls, context)
def execute(self, context):
rig, cam = get_rig_and_cam(context.active_object)
# get the local matrix of the aim bone
aim_loc = rig.pose.bones["Aim"].matrix_basis.to_translation()
# create a transform matrix for the z loc of the aim bone
mat_trans = mathutils.Matrix.Translation( [0, 0, aim_loc[2] + 1.7 ]) # Hardcoded height of rest position
# repostion the aim bone so it's above the root (using the original z value)
rig.pose.bones["Aim"].matrix = rig.pose.bones["Root"].matrix @ mat_trans
# offset the camera matrix relative to the new aim position
camera_offset_vector = (rig.pose.bones["Aim"].matrix_basis.to_translation() ) - aim_loc
camera_offset_matrix = mathutils.Matrix.Translation(camera_offset_vector)
rig.pose.bones["Camera"].matrix = rig.pose.bones["Camera"].matrix @ camera_offset_matrix
return {'FINISHED'}
class ADD_CAMERA_RIGS_OT_swap_lens(Operator):
bl_idname = "add_camera_rigs.swap_lens"
bl_label = "Swap Lens"
bl_description = "Set the focal length to a specific value and shift the camera to match the same framing"
bl_options = {'REGISTER', 'UNDO'}
camera_lens: bpy.props.FloatProperty(
name="Focal Length (mm)",
default=50,
min=1,
max=1000,
subtype='DISTANCE_CAMERA',
description="The value of the new focal length",
)
@classmethod
def poll(cls, context):
return poll_perspective(cls, context)
def draw(self, context):
layout = self.layout
row = layout.row()
row.label(text="Focal Length:")
row.prop(self, "camera_lens", text="")
row = layout.row()
def invoke(self, context, event):
rig, _cam = get_rig_and_cam(context.active_object)
self.camera_lens = rig.pose.bones["Camera"]["lens"]
return context.window_manager.invoke_props_popup(self, event)
def execute(self, context):
rig, cam = get_rig_and_cam(context.active_object)
# get the vector from aim to camera bone
vector = (rig.pose.bones["Aim"].matrix.to_translation()
- rig.pose.bones["Camera"].matrix.to_translation())
old_lens = rig.pose.bones["Camera"]["lens"]
new_lens = self.camera_lens
# set the new camera lens
rig.pose.bones["Camera"]["lens"] = new_lens
# set the new camera position, by offsetting it
# towards the aim bone proportionally to the lens change
loc = rig.pose.bones["Camera"].matrix.translation
loc += vector * (1.0 - new_lens / old_lens)
return {'FINISHED'}
classes = (
ADD_CAMERA_RIGS_OT_set_scene_camera,
ADD_CAMERA_RIGS_OT_add_marker_bind,
ADD_CAMERA_RIGS_OT_set_dof_bone,
ADD_CAMERA_RIGS_OT_set_dolly_zoom,
ADD_CAMERA_RIGS_OT_remove_dolly_zoom,
ADD_CAMERA_RIGS_OT_shift_to_pivot,
ADD_CAMERA_RIGS_OT_swap_lens,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
@@ -0,0 +1,48 @@
# SPDX-FileCopyrightText: 2019 Wayne Dixon
#
# SPDX-License-Identifier: GPL-3.0-or-later
from bpy.types import AddonPreferences
from bpy.props import StringProperty
class AddCameraRigsPreferences(AddonPreferences):
bl_idname = __package__
# Widget prefix
widget_prefix: StringProperty(
name="Camera Widget prefix",
description="Prefix for the widget objects",
default="WGT-",
)
# Collection name
camera_widget_collection_name: StringProperty(
name="Bone Widget collection name",
description="Name for the collection the widgets will appear",
default="Widgets",
)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, "widget_prefix", text="Widget Prefix")
col.prop(self, "camera_widget_collection_name", text="Collection name")
classes = (
AddCameraRigsPreferences,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
@@ -0,0 +1,228 @@
# SPDX-FileCopyrightText: 2019 Wayne Dixon
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from bpy.types import Menu, Panel
from .operators import get_rig_and_cam, poll_base
class CameraRigUIMixin():
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Item'
@classmethod
def poll(cls, context):
return poll_base(cls, context)
class ADD_CAMERA_RIGS_MT_lens_ops(Menu):
bl_label = "Camera Rig Lens Specials"
def draw(self, context):
active_object = context.active_object
_rig, cam = get_rig_and_cam(active_object)
cam_data = cam.data
layout = self.layout
drv = cam_data.animation_data.drivers[0]
if drv.driver.expression == "lens":
layout.operator("add_camera_rigs.set_dolly_zoom")
else:
layout.operator("add_camera_rigs.remove_dolly_zoom")
layout.operator("add_camera_rigs.shift_to_pivot")
layout.operator("add_camera_rigs.swap_lens")
class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigUIMixin):
bl_label = "Camera Rig"
def draw(self, context):
active_object = context.active_object
rig, cam = get_rig_and_cam(active_object)
pose_bones = rig.pose.bones
cam_data = cam.data
layout = self.layout
layout.use_property_split = True
# Camera lens
if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"):
col = layout.column(align=True)
row = col.row(align=False)
drv = cam_data.animation_data.drivers[0]
if cam_data.type == 'ORTHO':
row.prop(cam_data, "ortho_scale")
elif drv.driver.expression == "lens":
row.prop(pose_bones["Camera"], '["lens"]', text="Focal Length")
else:
row.prop(pose_bones["Camera"], '["lens_offset"]',
text="Lens Offset")
sub = col.row(align=False)
sub.enabled = False
sub.prop(cam_data, "lens")
row.menu("ADD_CAMERA_RIGS_MT_lens_ops", icon='DOWNARROW_HLT', text="")
col = layout.column(align=True)
col.prop(cam_data, "shift_x", text="Shift X")
col.prop(cam_data, "shift_y", text="Y")
# 2D rig stuff
elif rig["rig_id"].lower() == "2d_rig":
col = layout.column(align=True)
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")
sub = layout.column(align=True)
sub.prop(cam_data, "clip_start", text="Clip Start")
sub.prop(cam_data, "clip_end", text="End")
layout.prop(cam_data, "type")
class ADD_CAMERA_RIGS_PT_camera_rig_ui_dof(Panel, CameraRigUIMixin):
bl_label = "Depth of Field"
bl_parent_id = "ADD_CAMERA_RIGS_PT_camera_rig_ui"
def draw_header(self, context):
active_object = context.active_object
_rig, cam = get_rig_and_cam(active_object)
layout = self.layout
layout.prop(cam.data.dof, "use_dof", text="")
def draw(self, context):
active_object = context.active_object
rig, cam = get_rig_and_cam(active_object)
pose_bones = rig.pose.bones
cam_data = cam.data
layout = self.layout
layout.use_property_split = True
col = layout.column(align=False)
col.active = cam_data.dof.use_dof
if cam_data.dof.focus_object is None:
if rig["rig_id"].lower() == "2d_rig":
col.operator("add_camera_rigs.set_dof_bone", text="Setup DOF Bone")
else:
col.operator("add_camera_rigs.set_dof_bone")
sub = col.column(align=True)
sub.prop(cam_data.dof, "focus_object", text="Focus on Object")
if (cam_data.dof.focus_object is not None
and cam_data.dof.focus_object.type == 'ARMATURE'):
sub.prop_search(cam_data.dof, "focus_subtarget",
cam_data.dof.focus_object.data, "bones")
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")
class ADD_CAMERA_RIGS_PT_camera_rig_ui_viewport(Panel, CameraRigUIMixin):
bl_label = "Viewport Display"
bl_parent_id = "ADD_CAMERA_RIGS_PT_camera_rig_ui"
def draw(self, context):
active_object = context.active_object
_rig, cam = get_rig_and_cam(active_object)
cam_data = cam.data
layout = self.layout
layout.use_property_split = True
col = layout.column(align=False, heading="Show")
col.prop(active_object, 'show_in_front',
toggle=False, text='In Front')
col.prop(cam_data, "show_limits", text="Limits")
col = layout.column(align=False, heading="Passepartout")
col.use_property_decorate = False
row = col.row(align=True)
sub = row.row(align=True)
sub.prop(cam_data, "show_passepartout", text="")
sub = sub.row(align=True)
sub.active = cam_data.show_passepartout
sub.prop(cam_data, "passepartout_alpha", text="")
row.prop_decorator(cam_data, "passepartout_alpha")
# Composition guides
col.separator()
col.popover(
panel="ADD_CAMERA_RIGS_PT_composition_guides",
text="Composition Guides",
)
class ADD_CAMERA_RIGS_PT_camera_rig_ui_visibility(Panel, CameraRigUIMixin):
bl_label = "Rig Properties"
bl_parent_id = "ADD_CAMERA_RIGS_PT_camera_rig_ui"
def draw(self, context):
active_object = context.active_object
rig, cam = get_rig_and_cam(active_object)
pose_bones = rig.pose.bones
layout = self.layout
col = layout.column(align=True)
col.prop(cam, "hide_select", text="Make Camera Unselectable")
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')
layout.use_property_split = True
if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"):
# Track to Constraint
track_to_constraint = None
for con in pose_bones["Camera"].constraints:
if con.type == 'TRACK_TO':
track_to_constraint = con
break
if track_to_constraint is not None:
col = layout.column(align=True)
col.prop(track_to_constraint, 'influence',
text="Aim Lock", slider=True)
# Crane arm stuff
if rig["rig_id"].lower() == "crane_rig":
col = layout.column(align=True)
col.prop(pose_bones["Crane_Height"],
'scale', index=1, text="Crane Arm Height")
col.prop(pose_bones["Crane_Arm"],
'scale', index=1, text="Length")
elif rig["rig_id"].lower() == "2d_rig" and "MCH-DOF_Parent" in pose_bones:
# DOF constraint
copy_loc_constraint = None
for con in pose_bones["MCH-DOF_Parent"].constraints:
if con.type == 'COPY_LOCATION':
copy_loc_constraint = con
break
if copy_loc_constraint is not None:
col = layout.column(align=True)
col.prop(copy_loc_constraint, 'influence',
text="Aim DOF", slider=True)
def register():
bpy.utils.register_class(ADD_CAMERA_RIGS_MT_lens_ops)
bpy.utils.register_class(ADD_CAMERA_RIGS_PT_camera_rig_ui)
bpy.utils.register_class(ADD_CAMERA_RIGS_PT_camera_rig_ui_dof)
bpy.utils.register_class(ADD_CAMERA_RIGS_PT_camera_rig_ui_viewport)
bpy.utils.register_class(ADD_CAMERA_RIGS_PT_camera_rig_ui_visibility)
def unregister():
bpy.utils.unregister_class(ADD_CAMERA_RIGS_MT_lens_ops)
bpy.utils.unregister_class(ADD_CAMERA_RIGS_PT_camera_rig_ui)
bpy.utils.unregister_class(ADD_CAMERA_RIGS_PT_camera_rig_ui_dof)
bpy.utils.unregister_class(ADD_CAMERA_RIGS_PT_camera_rig_ui_viewport)
bpy.utils.unregister_class(ADD_CAMERA_RIGS_PT_camera_rig_ui_visibility)