diff --git a/add_camera_rigs/__init__.py b/add_camera_rigs/__init__.py index 6b5771993f8351bdc7a21fc7ff60fe0bea5202d1..6f9f9eeb43cf828e1936cb310c11c07ad370c1e5 100644 --- a/add_camera_rigs/__init__.py +++ b/add_camera_rigs/__init__.py @@ -18,8 +18,8 @@ bl_info = { "name": "Add Camera Rigs", - "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard", - "version": (1, 4, 2), + "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard, Flavio Perez", + "version": (1, 4, 3), "blender": (2, 80, 0), "location": "View3D > Add > Camera > Dolly or Crane Rig", "description": "Adds a Camera Rig with UI", diff --git a/add_camera_rigs/build_rigs.py b/add_camera_rigs/build_rigs.py index 1a37bc6e530a0956212727d69fe601db2c6415e0..57cadc0e3aded4e25b8bafc3090149f9f7f6f892 100644 --- a/add_camera_rigs/build_rigs.py +++ b/add_camera_rigs/build_rigs.py @@ -17,18 +17,33 @@ # ##### END GPL LICENSE BLOCK ##### import bpy -from bpy_extras import object_utils from bpy.types import Operator -from math import radians, pi +from bpy_extras import object_utils +from mathutils import Vector from rna_prop_ui import rna_idprop_ui_prop_get +from math import pi + from .create_widgets import (create_root_widget, - create_widget, - create_camera_widget, - create_aim_widget, - ) + create_camera_widget, create_aim_widget, + create_circle_widget, create_corner_widget) + + +def create_prop_driver(rig, cam, prop_from, prop_to): + """Create driver to a property on the rig""" + driver = cam.data.driver_add(prop_to) + driver.driver.type = 'SCRIPTED' + var = driver.driver.variables.new() + var.name = 'var' + var.type = 'SINGLE_PROP' + # Target the custom bone property + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from + driver.driver.expression = 'var' -def create_dolly_bones(rig, bone_layers): + +def create_dolly_bones(rig): + """Create bones for the dolly camera rig""" bones = rig.data.edit_bones # Add new bones @@ -39,7 +54,7 @@ def create_dolly_bones(rig, bone_layers): ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7) - ctrl_aim_child.layers = bone_layers + ctrl_aim_child.layers = tuple(i == 1 for i in range(32)) ctrl_aim = bones.new("Aim") ctrl_aim.head = (0.0, 10.0, 1.7) @@ -57,7 +72,8 @@ def create_dolly_bones(rig, bone_layers): ctrl_aim_child.parent = ctrl_aim -def create_crane_bones(rig, bone_layers): +def create_crane_bones(rig): + """Create bones for the crane camera rig""" bones = rig.data.edit_bones # Add new bones @@ -68,7 +84,7 @@ def create_crane_bones(rig, bone_layers): ctrl_aim_child = bones.new("Aim_shape_rotation-MCH") ctrl_aim_child.head = (0.0, 10.0, 1.7) ctrl_aim_child.tail = (0.0, 11.0, 1.7) - ctrl_aim_child.layers = bone_layers + ctrl_aim_child.layers = tuple(i == 1 for i in range(32)) ctrl_aim = bones.new("Aim") ctrl_aim.head = (0.0, 10.0, 1.7) @@ -112,34 +128,13 @@ def create_crane_bones(rig, bone_layers): pose_bones["Crane_height"].lock_scale = (True, False, True) -def build_camera_rig(context, mode): - bone_layers = tuple(i == 1 for i in range(32)) - view_layer = bpy.context.view_layer - - rig_name = mode.capitalize() + "_Rig" - rig_data = bpy.data.armatures.new(rig_name) - rig = object_utils.object_data_add(context, rig_data, name=rig_name) - rig["rig_id"] = "%s" % rig_name - view_layer.objects.active = rig - rig.location = context.scene.cursor.location - - bpy.ops.object.mode_set(mode='EDIT') - - # Add new bones - if mode == "DOLLY": - create_dolly_bones(rig, bone_layers) - elif mode == "CRANE": - create_crane_bones(rig, bone_layers) - +def setup_3d_rig(rig, cam): + """Finish setting up Dolly and Crane rigs""" # Jump into object mode and change bones to euler bpy.ops.object.mode_set(mode='OBJECT') pose_bones = rig.pose.bones - for b in pose_bones: - b.rotation_mode = 'XYZ' - - # Add custom properties to the armature’s Camera bone, - # so that all properties may be animated in a single action - # Add driver after the camera is created + for bone in pose_bones: + bone.rotation_mode = 'XYZ' # Lens property pb = pose_bones['Camera'] @@ -150,22 +145,6 @@ def build_camera_rig(context, mode): prop["max"] = 1000000.0 prop["soft_max"] = 5000.0 - # DOF Focus Distance property - pb = pose_bones['Camera'] - pb["focus_distance"] = 10.0 - prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True) - prop["default"] = 10.0 - prop["min"] = 0.0 - - # DOF F-Stop property - pb = pose_bones['Camera'] - pb["aperture_fstop"] = 2.8 - prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True) - prop["default"] = 2.8 - prop["min"] = 0.0 - prop["soft_min"] = 0.1 - prop["soft_max"] = 128.0 - # Build the widgets root_widget = create_root_widget("Camera_Root") camera_widget = create_camera_widget("Camera") @@ -176,7 +155,7 @@ def build_camera_rig(context, mode): pose_bones["Aim"].custom_shape = aim_widget pose_bones["Camera"].custom_shape = camera_widget - # Set the "At" field to the child + # Set the "At" field to the shape mecanism pose_bones["Aim"].custom_shape_transform = pose_bones["Aim_shape_rotation-MCH"] # Add constraints to bones @@ -189,55 +168,359 @@ def build_camera_rig(context, mode): con.subtarget = "Aim" con.use_target_z = True - # Change display to BBone: it just looks nicer - bpy.context.object.data.display_type = 'BBONE' - # Change display to wire for object - bpy.context.object.display_type = 'WIRE' + cam.data.display_size = 1.0 + cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x + + create_prop_driver(rig, cam, "lens", "lens") + + +def create_2d_bones(context, rig, cam): + """Create bones for the 2D camera rig""" + scene = context.scene + bones = rig.data.edit_bones + + # Add new bones + bones = rig.data.edit_bones + root = bones.new("Root") + root.tail = Vector((0.0, 0.0, 1.0)) + root.show_wire = True + + ctrl = bones.new('Camera') + ctrl.tail = Vector((0.0, 0.0, 1.0)) + ctrl.show_wire = True + + left_corner = bones.new("Left_corner") + left_corner.head = (-3, 10, -2) + left_corner.tail = left_corner.head + Vector((0.0, 0.0, 1.0)) + left_corner.show_wire = True + + right_corner = bones.new("Right_corner") + right_corner.head = (3, 10, -2) + right_corner.tail = right_corner.head + Vector((0.0, 0.0, 1.0)) + right_corner.show_wire = True + corner_distance_x = (left_corner.head - right_corner.head).length + corner_distance_y = -left_corner.head.z + corner_distance_z = left_corner.head.y + + center = bones.new("Center-MCH") + center.head = ((right_corner.head + left_corner.head) / 2.0) + center.tail = center.head + Vector((0.0, 0.0, 1.0)) + center.layers = tuple(i == 1 for i in range(32)) + center.show_wire = True + + # Setup hierarchy + ctrl.parent = root + left_corner.parent = root + right_corner.parent = root + center.parent = root + + # Jump into object mode and change bones to euler + bpy.ops.object.mode_set(mode='OBJECT') + pose_bones = rig.pose.bones + for bone in pose_bones: + bone.rotation_mode = 'XYZ' + + # Bone drivers + center_drivers = pose_bones["Center-MCH"].driver_add("location") + + # Center X driver + driver = center_drivers[0].driver + driver.type = 'AVERAGE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_X' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Center Y driver + driver = center_drivers[1].driver + driver.type = 'SCRIPTED' + + driver.expression = '({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + (left_y + right_y)/2'.format(distance_x=corner_distance_x) + + for direction in ('x', 'y'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'res_' + direction + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'SCENE' + var.targets[0].id = scene + var.targets[0].data_path = 'render.resolution_' + direction + + # Center Z driver + driver = center_drivers[2].driver + driver.type = 'AVERAGE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Bone constraints + con = pose_bones["Camera"].constraints.new('DAMPED_TRACK') + con.target = rig + con.subtarget = "Center-MCH" + con.track_axis = 'TRACK_NEGATIVE_Z' + + # Build the widgets + left_widget = create_corner_widget("Left_corner", reverse=True) + right_widget = create_corner_widget("Right_corner") + parent_widget = create_circle_widget("Root", radius=0.5) + camera_widget = create_circle_widget("Camera_2D", radius=0.3) + + # Add the custom bone shapes + pose_bones["Left_corner"].custom_shape = left_widget + pose_bones["Right_corner"].custom_shape = right_widget + pose_bones["Root"].custom_shape = parent_widget + pose_bones["Camera"].custom_shape = camera_widget + + # Lock the relevant loc, rot and scale + pose_bones["Left_corner"].lock_rotation = (True,) * 3 + pose_bones["Right_corner"].lock_rotation = (True,) * 3 + pose_bones["Camera"].lock_rotation = (True,) * 3 + pose_bones["Camera"].lock_scale = (True,) * 3 + + # Camera settings + + cam.data.sensor_fit = "HORIZONTAL" # Avoids distortion in portrait format + + # Property to switch between rotation and switch mode + pose_bones["Camera"]['rotation_shift'] = 0.0 + prop = rna_idprop_ui_prop_get(pose_bones["Camera"], 'rotation_shift', create=True) + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 0.0 + prop["soft_max"] = 1.0 + prop["description"] = 'rotation_shift' + + # Rotation / shift switch driver + driver = con.driver_add('influence').driver + driver.expression = '1 - rotation_shift' + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + # Focal length driver + driver = cam.data.driver_add('lens').driver + driver.expression = 'abs({distance_z} - (left_z + right_z)/2 + cam_z) * 36 / frame_width'.format(distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'frame_width' + var.type = 'LOC_DIFF' + var.targets[0].id = rig + var.targets[0].bone_target = "Left_corner" + var.targets[0].transform_space = 'WORLD_SPACE' + var.targets[1].id = rig + var.targets[1].bone_target = "Right_corner" + var.targets[1].transform_space = 'WORLD_SPACE' + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + '_z' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_z' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_Z' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Orthographic scale driver + driver = cam.data.driver_add('ortho_scale').driver + driver.expression = 'abs({distance_x} - (left_x - right_x))'.format(distance_x=corner_distance_x) + + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = corner + '_x' + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_X' + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + # Shift driver X + driver = cam.data.driver_add('shift_x').driver + + driver.expression = 'rotation_shift * (((left_x + right_x)/2 - cam_x) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36)'.format(distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + for direction in ('x', 'z'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_' + direction + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'lens' + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'CAMERA' + var.targets[0].id = cam.data + var.targets[0].data_path = 'lens' + + # Shift driver Y + driver = cam.data.driver_add('shift_y').driver + + driver.expression = 'rotation_shift * -(({distance_y} - (left_y + right_y)/2 + cam_y) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36 - (res_y/res_x)/2)'.format(distance_y=corner_distance_y, distance_z=corner_distance_z) + + var = driver.variables.new() + var.name = 'rotation_shift' + var.type = 'SINGLE_PROP' + var.targets[0].id = rig + var.targets[0].data_path = 'pose.bones["Camera"]["rotation_shift"]' + + for direction in ('y', 'z'): + for corner in ('left', 'right'): + var = driver.variables.new() + var.name = '%s_%s' % (corner, direction) + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = corner.capitalize() + '_corner' + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + var = driver.variables.new() + var.name = 'cam_' + direction + var.type = 'TRANSFORMS' + var.targets[0].id = rig + var.targets[0].bone_target = "Camera" + var.targets[0].transform_type = 'LOC_' + direction.upper() + var.targets[0].transform_space = 'TRANSFORM_SPACE' + + for direction in ('x', 'y'): + var = driver.variables.new() + var.name = 'res_' + direction + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'SCENE' + var.targets[0].id = scene + var.targets[0].data_path = 'render.resolution_' + direction + + var = driver.variables.new() + var.name = 'lens' + var.type = 'SINGLE_PROP' + var.targets[0].id_type = 'CAMERA' + var.targets[0].id = cam.data + var.targets[0].data_path = 'lens' + + +def build_camera_rig(context, mode): + """Create stuff common to all camera rigs.""" # Add the camera object cam_name = "%s_Camera" % mode.capitalize() cam_data = bpy.data.cameras.new(cam_name) cam = object_utils.object_data_add(context, cam_data, name=cam_name) - view_layer.objects.active = cam context.scene.camera = cam - cam.data.display_size = 1.0 - cam.rotation_euler[0] = pi / 2.0 # Rotate the camera 90 degrees in x + # Add the rig object + rig_name = mode.capitalize() + "_Rig" + rig_data = bpy.data.armatures.new(rig_name) + rig = object_utils.object_data_add(context, rig_data, name=rig_name) + rig["rig_id"] = "%s" % rig_name + rig.location = context.scene.cursor.location + + bpy.ops.object.mode_set(mode='EDIT') + # Add new bones and setup specific rigs + if mode == "DOLLY": + create_dolly_bones(rig) + setup_3d_rig(rig, cam) + elif mode == "CRANE": + create_crane_bones(rig) + setup_3d_rig(rig, cam) + elif mode == "2D": + create_2d_bones(context, rig, cam) + + # Parent the camera to the rig cam.location = (0.0, -1.0, 0.0) # Move the camera to the correct position cam.parent = rig cam.parent_type = "BONE" cam.parent_bone = "Camera" + # Change display to BBone: it just looks nicer + rig.data.display_type = 'BBONE' + # Change display to wire for object + rig.display_type = 'WIRE' + # Lock camera transforms cam.lock_location = (True,) * 3 cam.lock_rotation = (True,) * 3 cam.lock_scale = (True,) * 3 + # Add custom properties to the armature’s Camera bone, + # so that all properties may be animated in a single action + + pose_bones = rig.pose.bones + + # DOF Focus Distance property + pb = pose_bones['Camera'] + pb["focus_distance"] = 10.0 + prop = rna_idprop_ui_prop_get(pb, "focus_distance", create=True) + prop["default"] = 10.0 + prop["min"] = 0.0 + + # DOF F-Stop property + pb = pose_bones['Camera'] + pb["aperture_fstop"] = 2.8 + prop = rna_idprop_ui_prop_get(pb, "aperture_fstop", create=True) + prop["default"] = 2.8 + prop["min"] = 0.0 + prop["soft_min"] = 0.1 + prop["soft_max"] = 128.0 + # Add drivers to link the camera properties to the custom props # on the armature - for prop_from, prop_to in (("lens", "lens"), - ("focus_distance", "dof.focus_distance"), - ("aperture_fstop", "dof.aperture_fstop")): - driver = cam.data.driver_add(prop_to) - driver.driver.type = 'SCRIPTED' - var = driver.driver.variables.new() - var.name = 'var' - var.type = 'SINGLE_PROP' - - # Target the custom bone property - var.targets[0].id = rig - var.targets[0].data_path = 'pose.bones["Camera"]["%s"]' % prop_from - driver.driver.expression = 'var' + create_prop_driver(rig, cam, "focus_distance", "dof.focus_distance") + create_prop_driver(rig, cam, "aperture_fstop", "dof.aperture_fstop") # Make the rig the active object - for ob in view_layer.objects: - ob.select_set(False) + view_layer = context.view_layer + for obj in view_layer.objects: + obj.select_set(False) rig.select_set(True) view_layer.objects.active = rig - return rig - class OBJECT_OT_build_camera_rig(Operator): bl_idname = "object.build_camera_rig" @@ -245,11 +528,12 @@ class OBJECT_OT_build_camera_rig(Operator): bl_description = "Build a Camera Rig" bl_options = {'REGISTER', 'UNDO'} - mode: bpy.props.EnumProperty(items= - (('DOLLY',) * 3, - ('CRANE',) * 3,), + mode: bpy.props.EnumProperty(items=(('DOLLY', 'Dolly', 'Dolly rig'), + ('CRANE', 'Crane', 'Crane rig',), + ('2D', '2D', '2D rig')), name="mode", - description="", default="DOLLY") + description="Type of camera to create", + default="DOLLY") def execute(self, context): # Build the rig @@ -260,18 +544,23 @@ class OBJECT_OT_build_camera_rig(Operator): def add_dolly_crane_buttons(self, context): """Dolly and crane entries in the Add Object > Camera Menu""" if context.mode == 'OBJECT': - op = self.layout.operator( + self.layout.operator( OBJECT_OT_build_camera_rig.bl_idname, text="Dolly Camera Rig", - icon='CAMERA_DATA' - ) - op.mode = "DOLLY" - op = self.layout.operator( + icon='VIEW_CAMERA' + ).mode = "DOLLY" + + self.layout.operator( OBJECT_OT_build_camera_rig.bl_idname, text="Crane Camera Rig", - icon='CAMERA_DATA' - ) - op.mode = "CRANE" + icon='VIEW_CAMERA' + ).mode = "CRANE" + + self.layout.operator( + OBJECT_OT_build_camera_rig.bl_idname, + text="2D Camera Rig", + icon='PIVOT_BOUNDBOX' + ).mode = "2D" classes = ( diff --git a/add_camera_rigs/composition_guides_menu.py b/add_camera_rigs/composition_guides_menu.py index c3ff43e5a12cfdfac3a0facb8ef7e1411fd09814..165375257ba7fc6db4d01af45981bfd28f76d4c4 100644 --- a/add_camera_rigs/composition_guides_menu.py +++ b/add_camera_rigs/composition_guides_menu.py @@ -19,7 +19,7 @@ import bpy from bpy.types import Panel -from .operators import get_arm_and_cam +from .operators import get_rig_and_cam class ADD_CAMERA_RIGS_PT_composition_guides(Panel): bl_label = "Composition Guides" @@ -29,7 +29,7 @@ class ADD_CAMERA_RIGS_PT_composition_guides(Panel): def draw(self, context): layout = self.layout - arm, cam = get_arm_and_cam(context.active_object) + rig, cam = get_rig_and_cam(context.active_object) cam = cam.data layout.prop(cam, "show_safe_areas") diff --git a/add_camera_rigs/create_widgets.py b/add_camera_rigs/create_widgets.py index f19fc206e9b0e744a37163e1ba55563b8bd5f4b2..72a8c70d071041aecba28629870b8deebd42051e 100644 --- a/add_camera_rigs/create_widgets.py +++ b/add_camera_rigs/create_widgets.py @@ -1,4 +1,24 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + import bpy +from mathutils import Vector +from math import cos, sin, pi def create_widget(name): @@ -29,6 +49,44 @@ def create_widget(name): return obj +def create_corner_widget(name, reverse=False): + """Create a wedge-shaped widget""" + obj = create_widget(name) + if not obj.data.vertices: + reverse = -1 if reverse else 1 + verts = (Vector((reverse * 0.0, 0.0, 0.0)), + Vector((reverse * 0.0, 1.0, 0.0)), + Vector((reverse * -0.1, 1.0, 0.0)), + Vector((reverse * -0.1, 0.1, 0.0)), + Vector((reverse * -1.0, 0.1, 0.0)), + Vector((reverse * -1.0, 0.0, 0.0)), + ) + edges = [(n, (n+1) % len(verts)) for n in range(len(verts))] + + mesh = obj.data + mesh.from_pydata(verts, edges, ()) + mesh.update() + return obj + + +def create_circle_widget(name, radius=1.0): + """Create a circle-shaped widget""" + obj = create_widget(name) + if not obj.data.vertices: + vert_n = 16 + verts = [] + for n in range(vert_n): + angle = n / vert_n * 2*pi + verts.append(Vector((cos(angle) * radius, + sin(angle) * radius, 0.0))) + edges = [(n, (n+1) % len(verts)) for n in range(len(verts))] + + mesh = obj.data + mesh.from_pydata(verts, edges, ()) + mesh.update() + return obj + + def create_root_widget(name): """Create a compass-shaped widget""" obj = create_widget(name) diff --git a/add_camera_rigs/operators.py b/add_camera_rigs/operators.py index 7468007e5de6db641d70fca0a161763fb351ca80..058ed14667e4c20d8e32191aa5081da1ed002cfc 100644 --- a/add_camera_rigs/operators.py +++ b/add_camera_rigs/operators.py @@ -20,7 +20,7 @@ import bpy from bpy.types import Operator -def get_arm_and_cam(obj): +def get_rig_and_cam(obj): if obj.type == 'ARMATURE': cam = None for child in obj.children: @@ -32,7 +32,8 @@ def get_arm_and_cam(obj): elif (obj.type == 'CAMERA' and obj.parent is not None and "rig_id" in obj.parent - and obj.parent["rig_id"].lower() in {"dolly_rig", "crane_rig"}): + and obj.parent["rig_id"].lower() in {"dolly_rig", + "crane_rig", "2d_rig"}): return obj.parent, obj return None, None @@ -41,25 +42,31 @@ class CameraRigMixin(): @classmethod def poll(cls, context): if context.active_object is not None: - return get_arm_and_cam(context.active_object) != (None, None) + return get_rig_and_cam(context.active_object) != (None, None) return False -class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator, CameraRigMixin): +class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator): bl_idname = "add_camera_rigs.set_scene_camera" bl_label = "Make Camera Active" bl_description = "Makes the camera parented to this rig the active scene camera" + @classmethod + def poll(cls, context): + if context.active_object is not None: + rig, cam = get_rig_and_cam(context.active_object) + if cam is not None: + return cam is not context.scene.camera + + return False + def execute(self, context): - arm, cam = get_arm_and_cam(context.active_object) + rig, cam = get_rig_and_cam(context.active_object) scene_cam = context.scene.camera - if cam is not None and cam is not scene_cam: - context.scene.camera = cam - return {'FINISHED'} - - return {'CANCELLED'} + context.scene.camera = cam + return {'FINISHED'} class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin): @@ -68,7 +75,7 @@ class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin): bl_description = "Add marker to current frame then bind rig camera to it (for camera switching)" def execute(self, context): - arm, cam = get_arm_and_cam(context.active_object) + rig, cam = get_rig_and_cam(context.active_object) marker = context.scene.timeline_markers.new( "cam_" + str(context.scene.frame_current), @@ -85,15 +92,15 @@ class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin): bl_description = "Create Empty and add as DOF Object" def execute(self, context): - arm, cam = get_arm_and_cam(context.active_object) - bone = arm.data.bones['Aim_shape_rotation-MCH'] + rig, cam = get_rig_and_cam(context.active_object) + bone = rig.data.bones['Aim_shape_rotation-MCH'] # Add Empty empty_obj = bpy.data.objects.new("EmptyDOF", None) context.scene.collection.objects.link(empty_obj) # Parent to Aim Child bone - empty_obj.parent = arm + empty_obj.parent = rig empty_obj.parent_type = "BONE" empty_obj.parent_bone = "Aim_shape_rotation-MCH" diff --git a/add_camera_rigs/ui_panels.py b/add_camera_rigs/ui_panels.py index 63fe158a6b0e9e7281a0f81d1d3f235cd87c5dbb..0dc3c69e54fe024c802ad226eb5bb6872a20d25f 100644 --- a/add_camera_rigs/ui_panels.py +++ b/add_camera_rigs/ui_panels.py @@ -19,7 +19,7 @@ import bpy from bpy.types import Panel -from .operators import get_arm_and_cam, CameraRigMixin +from .operators import get_rig_and_cam, CameraRigMixin class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin): @@ -30,66 +30,87 @@ class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin): def draw(self, context): active_object = context.active_object - arm, cam = get_arm_and_cam(context.active_object) - pose_bones = arm.pose.bones + rig, cam = get_rig_and_cam(context.active_object) + pose_bones = rig.pose.bones cam_data = cam.data + layout = self.layout + + # Camera lens + if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"): + layout.prop(pose_bones["Camera"], '["lens"]', + text="Focal Length (mm)") + + col = layout.column(align=True) + col.label(text="Clipping:") + col.prop(cam_data, "clip_start", text="Start") + col.prop(cam_data, "clip_end", text="End") - layout = self.layout.box().column() - layout.label(text="Clipping:") - layout.prop(cam_data, "clip_start", text="Start") - layout.prop(cam_data, "clip_end", text="End") layout.prop(cam_data, "type") - layout.prop(cam_data.dof, "use_dof") - if cam_data.dof.use_dof: - if cam_data.dof.focus_object is None: - layout.operator("add_camera_rigs.add_dof_object", - text="Add DOF Empty", icon="OUTLINER_OB_EMPTY") - layout.prop(pose_bones["Camera"], - '["focus_distance"]', text="Focus Distance") - layout.prop(pose_bones["Camera"], - '["aperture_fstop"]', text="F-Stop") + # DoF + col = layout.column(align=True) + col.prop(cam_data.dof, "use_dof") + if cam_data.dof.use_dof: + if rig["rig_id"].lower() in ("crane_rig", "dolly_rig"): + if cam_data.dof.focus_object is None: + col.operator("add_camera_rigs.add_dof_object", + text="Add DOF Empty", icon="OUTLINER_OB_EMPTY") + else: + col.prop(cam_data.dof, "focus_object") + row = col.row(align=True) + row.active = cam_data.dof.focus_object is None + row.prop(pose_bones["Camera"], + '["focus_distance"]', text="Focus Distance") + col.prop(pose_bones["Camera"], + '["aperture_fstop"]', text="F-Stop") + + # Viewport display layout.prop(active_object, 'show_in_front', toggle=False, text='Show in Front') layout.prop(cam_data, "show_limits") - layout.prop(cam_data, "show_passepartout") + col = layout.column(align=True) + col.prop(cam_data, "show_passepartout") if cam_data.show_passepartout: - layout.prop(cam_data, "passepartout_alpha") + col.prop(cam_data, "passepartout_alpha") - layout.row().separator() - # Added the comp guides here + # Composition guides layout.popover( panel="ADD_CAMERA_RIGS_PT_composition_guides", text="Composition Guides",) - layout.row().separator() - layout.prop(cam, + # Props and operators + col = layout.column(align=True) + col.prop(cam, "hide_select", text="Make Camera Unselectable") - - layout.operator("add_camera_rigs.add_marker_bind", - text="Add Marker and Bind", icon="MARKER_HLT") - if context.scene.camera is not cam: - layout.operator("add_camera_rigs.set_scene_camera", - text="Make Camera Active", icon='CAMERA_DATA') - - # Camera lens - layout.separator() - layout.prop(pose_bones["Camera"], '["lens"]', text="Focal Length (mm)") - - # Track to Constraint - layout.label(text="Tracking:") - layout.prop(pose_bones["Camera"].constraints["Track To"], - 'influence', text="Aim Lock", slider=True) - - if arm["rig_id"].lower() == "crane_rig": - col = layout.box().column() + col.operator("add_camera_rigs.add_marker_bind", + text="Add Marker and Bind", icon="MARKER_HLT") + col.operator("add_camera_rigs.set_scene_camera", + text="Make Camera Active", icon='CAMERA_DATA') + + if rig["rig_id"].lower() in ("dolly_rig", "crane_rig"): + # Track to Constraint + col = layout.column(align=True) + col.label(text="Tracking:") + col.prop(pose_bones["Camera"].constraints["Track To"], + 'influence', text="Aim Lock", slider=True) # Crane arm stuff - col.label(text="Crane Arm:") - col.prop(pose_bones["Crane_height"], - 'scale', index=1, text="Arm Height") - col.prop(pose_bones["Crane_arm"], - 'scale', index=1, text="Arm Length") + if rig["rig_id"].lower() == "crane_rig": + col = layout.column(align=True) + col.label(text="Crane Arm:") + col.prop(pose_bones["Crane_height"], + 'scale', index=1, text="Arm Height") + col.prop(pose_bones["Crane_arm"], + 'scale', index=1, text="Arm Length") + + # 2D rig stuff + elif rig["rig_id"].lower() == "2d_rig": + col = layout.column(align=True) + col.label(text="2D Rig:") + col.prop(pose_bones["Camera"], '["rotation_shift"]', + text="Rotation/Shift") + if cam.data.sensor_width != 36: + col.label(text="Please set Camera Sensor Width to 36", icon="ERROR") def register():