diff options
Diffstat (limited to 'add_camera_rigs/build_rigs.py')
-rw-r--r-- | add_camera_rigs/build_rigs.py | 465 |
1 files changed, 377 insertions, 88 deletions
diff --git a/add_camera_rigs/build_rigs.py b/add_camera_rigs/build_rigs.py index 1a37bc6e..57cadc0e 100644 --- a/add_camera_rigs/build_rigs.py +++ b/add_camera_rigs/build_rigs.py @@ -17,18 +17,33 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -from bpy_extras import object_utils from bpy.types import Operator -from math import radians, pi +from bpy_extras import object_utils +from mathutils import Vector from rna_prop_ui import rna_idprop_ui_prop_get +from math import pi + from .create_widgets import (create_root_widget, - create_widget, - create_camera_widget, - create_aim_widget, - ) + create_camera_widget, create_aim_widget, + create_circle_widget, create_corner_widget) + + +def create_prop_driver(rig, cam, prop_from, prop_to): + """Create driver to a property on the rig""" + driver = cam.data.driver_add(prop_to) + driver.driver.type = 'SCRIPTED' + var = driver.driver.variables.new() + var.name = 'var' + var.type = 'SINGLE_PROP' + # Target the custom bone property + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from + driver.driver.expression = 'var' -def create_dolly_bones(rig, bone_layers): + +def create_dolly_bones(rig): + """Create bones for the dolly camera rig""" bones = rig.data.edit_bones # Add new bones @@ -39,7 +54,7 @@ def create_dolly_bones(rig, bone_layers): ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7) - ctrl_aim_child.layers = bone_layers + ctrl_aim_child.layers = tuple(i == 1 for i in range(32)) ctrl_aim = bones.new("Aim") ctrl_aim.head = (0.0, 10.0, 1.7) @@ -57,7 +72,8 @@ def create_dolly_bones(rig, bone_layers): ctrl_aim_child.parent = ctrl_aim -def create_crane_bones(rig, bone_layers): +def create_crane_bones(rig): + """Create bones for the crane camera rig""" bones = rig.data.edit_bones # Add new bones @@ -68,7 +84,7 @@ def create_crane_bones(rig, bone_layers): ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7) - ctrl_aim_child.layers = bone_layers + ctrl_aim_child.layers = tuple(i == 1 for i in range(32)) ctrl_aim = bones.new("Aim") ctrl_aim.head = (0.0, 10.0, 1.7) @@ -112,34 +128,13 @@ def create_crane_bones(rig, bone_layers): pose_bones["Crane_height"].lock_scale = (True, False, True) -def build_camera_rig(context, mode): - bone_layers = tuple(i == 1 for i in range(32)) - view_layer = bpy.context.view_layer - - rig_name = mode.capitalize() + "_Rig" - rig_data = bpy.data.armatures.new(rig_name) - rig = object_utils.object_data_add(context, rig_data, name=rig_name) - rig["rig_id"] = "%s" % rig_name - view_layer.objects.active = rig - rig.location = context.scene.cursor.location - - bpy.ops.object.mode_set(mode='EDIT') - - # Add new bones - if mode == "DOLLY": - create_dolly_bones(rig, bone_layers) - elif mode == "CRANE": - create_crane_bones(rig, bone_layers) - +def setup_3d_rig(rig, cam): + """Finish setting up Dolly and Crane rigs""" # Jump into object mode and change bones to euler bpy.ops.object.mode_set(mode='OBJECT') pose_bones = rig.pose.bones - for b in pose_bones: - b.rotation_mode = 'XYZ' - - # Add custom properties to the armature’s Camera bone, - # so that all properties may be animated in a single action - # Add driver after the camera is created + for bone in pose_bones: + bone.rotation_mode = 'XYZ' # Lens property pb = pose_bones['Camera'] @@ -150,22 +145,6 @@ def build_camera_rig(context, mode): prop["max"] = 1000000.0 prop["soft_max"] = 5000.0 - # DOF Focus Distance property - pb = pose_bones['Camera'] - pb["focus_distance"] = 10.0 - prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True) - prop["default"] = 10.0 - prop["min"] = 0.0 - - # DOF F-Stop property - pb = pose_bones['Camera'] - pb["aperture_fstop"] = 2.8 - prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True) - prop["default"] = 2.8 - prop["min"] = 0.0 - prop["soft_min"] = 0.1 - prop["soft_max"] = 128.0 - # Build the widgets root_widget = create_root_widget("Camera_Root") camera_widget = create_camera_widget("Camera") @@ -176,7 +155,7 @@ def build_camera_rig(context, mode): pose_bones["Aim"].custom_shape = aim_widget pose_bones["Camera"].custom_shape = camera_widget - # Set the "At" field to the child + # Set the "At" field to the shape mecanism pose_bones["Aim"].custom_shape_transform = pose_bones["Aim_shape_rotation-MCH"] # Add constraints to bones @@ -189,55 +168,359 @@ def build_camera_rig(context, mode): con.subtarget = "Aim" con.use_target_z = True - # Change display to BBone: it just looks nicer - bpy.context.object.data.display_type = 'BBONE' - # Change display to wire for object - bpy.context.object.display_type = 'WIRE' + cam.data.display_size = 1.0 + cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x + + create_prop_driver(rig, cam, "lens", "lens") + + +def create_2d_bones(context, rig, cam): + """Create bones for the 2D camera rig""" + scene = context.scene + bones = rig.data.edit_bones + + # Add new bones + bones = rig.data.edit_bones + root = bones.new("Root") + root.tail = Vector((0.0, 0.0, 1.0)) + root.show_wire = True + + ctrl = bones.new('Camera') + ctrl.tail = Vector((0.0, 0.0, 1.0)) + ctrl.show_wire = True + + left_corner = bones.new("Left_corner") + left_corner.head = (-3, 10, -2) + left_corner.tail = left_corner.head + Vector((0.0, 0.0, 1.0)) + left_corner.show_wire = True + + right_corner = bones.new("Right_corner") + right_corner.head = (3, 10, -2) + right_corner.tail = right_corner.head + Vector((0.0, 0.0, 1.0)) + right_corner.show_wire = True + corner_distance_x = (left_corner.head - right_corner.head).length + corner_distance_y = -left_corner.head.z + corner_distance_z = left_corner.head.y + + center = bones.new("Center-MCH") + center.head = ((right_corner.head + left_corner.head) / 2.0) + center.tail = center.head + Vector((0.0, 0.0, 1.0)) + center.layers = tuple(i == 1 for i in range(32)) + center.show_wire = True + + # Setup hierarchy + ctrl.parent = root + left_corner.parent = root + right_corner.parent = root + center.parent = root + + # Jump into object mode and change bones to euler + bpy.ops.object.mode_set(mode='OBJECT') + pose_bones = rig.pose.bones + for bone in pose_bones: + bone.rotation_mode = 'XYZ' + + # Bone drivers + center_drivers = pose_bones["Center-MCH"].driver_add("location") + + # Center X driver + driver = center_drivers[0].driver + driver.type = 'AVERAGE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_X' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Center Y driver + driver = center_drivers[1].driver + driver.type = 'SCRIPTED' + + driver.expression = '({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + (left_y + right_y)/2'.format(distance_x=corner_distance_x) + + for direction in ('x', 'y'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'res_' + direction + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'SCENE' + var.targets[0].id = scene + var.targets[0].data_path = 'render.resolution_' + direction + + # Center Z driver + driver = center_drivers[2].driver + driver.type = 'AVERAGE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Bone constraints + con = pose_bones["Camera"].constraints.new('DAMPED_TRACK') + con.target = rig + con.subtarget = "Center-MCH" + con.track_axis = 'TRACK_NEGATIVE_Z' + + # Build the widgets + left_widget = create_corner_widget("Left_corner", reverse=True) + right_widget = create_corner_widget("Right_corner") + parent_widget = create_circle_widget("Root", radius=0.5) + camera_widget = create_circle_widget("Camera_2D", radius=0.3) + + # Add the custom bone shapes + pose_bones["Left_corner"].custom_shape = left_widget + pose_bones["Right_corner"].custom_shape = right_widget + pose_bones["Root"].custom_shape = parent_widget + pose_bones["Camera"].custom_shape = camera_widget + + # Lock the relevant loc, rot and scale + pose_bones["Left_corner"].lock_rotation = (True,) * 3 + pose_bones["Right_corner"].lock_rotation = (True,) * 3 + pose_bones["Camera"].lock_rotation = (True,) * 3 + pose_bones["Camera"].lock_scale = (True,) * 3 + + # Camera settings + + cam.data.sensor_fit = "HORIZONTAL" # Avoids distortion in portrait format + + # Property to switch between rotation and switch mode + pose_bones["Camera"]['rotation_shift'] = 0.0 + prop = rna_idprop_ui_prop_get(pose_bones["Camera"], 'rotation_shift', create=True) + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + prop["description"] = 'rotation_shift' + + # Rotation / shift switch driver + driver = con.driver_add('influence').driver + driver.expression = '1 - rotation_shift' + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + # Focal length driver + driver = cam.data.driver_add('lens').driver + driver.expression = 'abs({distance_z} - (left_z + right_z)/2 + cam_z) * 36 / frame_width'.format(distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'frame_width' + var.type = 'LOC_DIFF' + var.targets[0].id = rig + var.targets[0].bone_target = "Left_corner" + var.targets[0].transform_space = 'WORLD_SPACE' + var.targets[1].id = rig + var.targets[1].bone_target = "Right_corner" + var.targets[1].transform_space = 'WORLD_SPACE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + '_z' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_z' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Orthographic scale driver + driver = cam.data.driver_add('ortho_scale').driver + driver.expression = 'abs({distance_x} - (left_x - right_x))'.format(distance_x=corner_distance_x) + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + '_x' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_X' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Shift driver X + driver = cam.data.driver_add('shift_x').driver + + driver.expression = 'rotation_shift * (((left_x + right_x)/2 - cam_x) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36)'.format(distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + for direction in ('x', 'z'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_' + direction + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'lens' + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'CAMERA' + var.targets[0].id = cam.data + var.targets[0].data_path = 'lens' + + # Shift driver Y + driver = cam.data.driver_add('shift_y').driver + + driver.expression = 'rotation_shift * -(({distance_y} - (left_y + right_y)/2 + cam_y) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36 - (res_y/res_x)/2)'.format(distance_y=corner_distance_y, distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + for direction in ('y', 'z'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_' + direction + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + for direction in ('x', 'y'): + var = driver.variables.new() + var.name = 'res_' + direction + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'SCENE' + var.targets[0].id = scene + var.targets[0].data_path = 'render.resolution_' + direction + + var = driver.variables.new() + var.name = 'lens' + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'CAMERA' + var.targets[0].id = cam.data + var.targets[0].data_path = 'lens' + + +def build_camera_rig(context, mode): + """Create stuff common to all camera rigs.""" # Add the camera object cam_name = "%s_Camera" % mode.capitalize() cam_data = bpy.data.cameras.new(cam_name) cam = object_utils.object_data_add(context, cam_data, name=cam_name) - view_layer.objects.active = cam context.scene.camera = cam - cam.data.display_size = 1.0 - cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x + # Add the rig object + rig_name = mode.capitalize() + "_Rig" + rig_data = bpy.data.armatures.new(rig_name) + rig = object_utils.object_data_add(context, rig_data, name=rig_name) + rig["rig_id"] = "%s" % rig_name + rig.location = context.scene.cursor.location + + bpy.ops.object.mode_set(mode='EDIT') + # Add new bones and setup specific rigs + if mode == "DOLLY": + create_dolly_bones(rig) + setup_3d_rig(rig, cam) + elif mode == "CRANE": + create_crane_bones(rig) + setup_3d_rig(rig, cam) + elif mode == "2D": + create_2d_bones(context, rig, cam) + + # Parent the camera to the rig cam.location = (0.0, -1.0, 0.0) # Move the camera to the correct position cam.parent = rig cam.parent_type = "BONE" cam.parent_bone = "Camera" + # Change display to BBone: it just looks nicer + rig.data.display_type = 'BBONE' + # Change display to wire for object + rig.display_type = 'WIRE' + # Lock camera transforms cam.lock_location = (True,) * 3 cam.lock_rotation = (True,) * 3 cam.lock_scale = (True,) * 3 + # Add custom properties to the armature’s Camera bone, + # so that all properties may be animated in a single action + + pose_bones = rig.pose.bones + + # DOF Focus Distance property + pb = pose_bones['Camera'] + pb["focus_distance"] = 10.0 + prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True) + prop["default"] = 10.0 + prop["min"] = 0.0 + + # DOF F-Stop property + pb = pose_bones['Camera'] + pb["aperture_fstop"] = 2.8 + prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True) + prop["default"] = 2.8 + prop["min"] = 0.0 + prop["soft_min"] = 0.1 + prop["soft_max"] = 128.0 + # Add drivers to link the camera properties to the custom props # on the armature - for prop_from, prop_to in (("lens", "lens"), - ("focus_distance", "dof.focus_distance"), - ("aperture_fstop", "dof.aperture_fstop")): - driver = cam.data.driver_add(prop_to) - driver.driver.type = 'SCRIPTED' - var = driver.driver.variables.new() - var.name = 'var' - var.type = 'SINGLE_PROP' - - # Target the custom bone property - var.targets[0].id = rig - var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from - driver.driver.expression = 'var' + create_prop_driver(rig, cam, "focus_distance", "dof.focus_distance") + create_prop_driver(rig, cam, "aperture_fstop", "dof.aperture_fstop") # Make the rig the active object - for ob in view_layer.objects: - ob.select_set(False) + view_layer = context.view_layer + for obj in view_layer.objects: + obj.select_set(False) rig.select_set(True) view_layer.objects.active = rig - return rig - class OBJECT_OT_build_camera_rig(Operator): bl_idname = "object.build_camera_rig" @@ -245,11 +528,12 @@ class OBJECT_OT_build_camera_rig(Operator): bl_description = "Build a Camera Rig" bl_options = {'REGISTER', 'UNDO'} - mode: bpy.props.EnumProperty(items= - (('DOLLY',) * 3, - ('CRANE',) * 3,), + mode: bpy.props.EnumProperty(items=(('DOLLY', 'Dolly', 'Dolly rig'), + ('CRANE', 'Crane', 'Crane rig',), + ('2D', '2D', '2D rig')), name="mode", - description="", default="DOLLY") + description="Type of camera to create", + default="DOLLY") def execute(self, context): # Build the rig @@ -260,18 +544,23 @@ class OBJECT_OT_build_camera_rig(Operator): def add_dolly_crane_buttons(self, context): """Dolly and crane entries in the Add Object > Camera Menu""" if context.mode == 'OBJECT': - op = self.layout.operator( + self.layout.operator( OBJECT_OT_build_camera_rig.bl_idname, text="Dolly Camera Rig", - icon='CAMERA_DATA' - ) - op.mode = "DOLLY" - op = self.layout.operator( + icon='VIEW_CAMERA' + ).mode = "DOLLY" + + self.layout.operator( OBJECT_OT_build_camera_rig.bl_idname, text="Crane Camera Rig", - icon='CAMERA_DATA' - ) - op.mode = "CRANE" + icon='VIEW_CAMERA' + ).mode = "CRANE" + + self.layout.operator( + OBJECT_OT_build_camera_rig.bl_idname, + text="2D Camera Rig", + icon='PIVOT_BOUNDBOX' + ).mode = "2D" classes = ( |