2025-07-01
This commit is contained in:
@@ -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 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
|
||||
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)
|
||||
Reference in New Issue
Block a user