Skip to content
Snippets Groups Projects
Commit 81ed56cb authored by Damien Picard's avatar Damien Picard
Browse files

add_camera_rigs: refactor and cleanup

- Fix widgets’ names: they were hardcoded and didn’t follow the
  preferences, leading to crashes.
- The UI was put back into the Item category, instead of Create,
  because it is not related to object creation.
- Fix some strange topology in two widget shapes.
- UI and operators use a new poll method, so that they work when
  either the rig or the camera is selected.
- The composition guides UI was converted to a panel, so that they may
  be drag-selected.
- Marker binding and DOF object operators were converted to the
  `bpy.data` API, making them simpler.
- Bones were moved around so that they are more similar between rigs.
  - They were scaled down to be 1 unit long, a simpler length — for
    instance, widgets are the same size as modeled. Widgets were
    scaled up to compensate.
  - The camera and aim bones were placed at 1.7 unit high, to be
    approximately at a standing human’s eyes’ height if the scene is
    in meters.
- Much of the rig generation was refactored to deduplicate code
  between the two rig types.
- Automatic renaming to `.000` was removed, since Blender already
  handles duplicate names.
- Widget prefix and collection were renamed to `WGT-` and `Widgets`
  respectively. This is to be closer to Rigify, hopefully unifying
  them.
- The GPL license header was added to every file.
- Some cleanup was done to better respect Python’s PEP 8.

Reviewed By: Wayne Dixon

Differential Revision: https://developer.blender.org/D6543
parent f5f442a7
No related branches found
No related tags found
No related merge requests found
...@@ -16,11 +16,10 @@ ...@@ -16,11 +16,10 @@
# #
# ##### END GPL LICENSE BLOCK ##### # ##### END GPL LICENSE BLOCK #####
bl_info = { bl_info = {
"name": "Add Camera Rigs", "name": "Add Camera Rigs",
"author": "Wayne Dixon, Brian Raschko, Kris Wittig", "author": "Wayne Dixon, Brian Raschko, Kris Wittig, Damien Picard",
"version": (1, 4, 1), "version": (1, 4, 2),
"blender": (2, 80, 0), "blender": (2, 80, 0),
"location": "View3D > Add > Camera > Dolly or Crane Rig", "location": "View3D > Add > Camera > Dolly or Crane Rig",
"description": "Adds a Camera Rig with UI", "description": "Adds a Camera Rig with UI",
......
This diff is collapsed.
# ##### 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 import bpy
from bpy.types import Menu from bpy.types import Panel
from .operators import get_arm_and_cam
class ADD_CAMERA_RIGS_MT_composition_guides_menu(Menu): class ADD_CAMERA_RIGS_PT_composition_guides(Panel):
bl_label = "Composition Guides" bl_label = "Composition Guides"
bl_idname = "ADD_CAMERA_RIGS_MT_composition_guides_menu" bl_space_type = 'VIEW_3D'
bl_region_type = 'HEADER'
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
activeCameraName = bpy.context.active_object.children[0].name arm, cam = get_arm_and_cam(context.active_object)
cam = bpy.data.cameras[bpy.data.objects[activeCameraName].data.name] cam = cam.data
layout.prop(cam, "show_safe_areas") layout.prop(cam, "show_safe_areas")
layout.row().separator() layout.row().separator()
...@@ -24,17 +44,12 @@ class ADD_CAMERA_RIGS_MT_composition_guides_menu(Menu): ...@@ -24,17 +44,12 @@ class ADD_CAMERA_RIGS_MT_composition_guides_menu(Menu):
layout.prop(cam, "show_composition_thirds") layout.prop(cam, "show_composition_thirds")
def draw_item(self, context):
layout = self.layout
layout.menu(CustomMenu.bl_idname)
def register(): def register():
bpy.utils.register_class(ADD_CAMERA_RIGS_MT_composition_guides_menu) bpy.utils.register_class(ADD_CAMERA_RIGS_PT_composition_guides)
def unregister(): def unregister():
bpy.utils.unregister_class(ADD_CAMERA_RIGS_MT_composition_guides_menu) bpy.utils.unregister_class(ADD_CAMERA_RIGS_PT_composition_guides)
if __name__ == "__main__": if __name__ == "__main__":
......
This diff is collapsed.
# ##### 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 import bpy
from bpy.types import Operator from bpy.types import Operator
def set_scene_camera(): def get_arm_and_cam(obj):
'''Makes the camera the active and sets it to the scene camera''' if obj.type == 'ARMATURE':
ob = bpy.context.active_object cam = None
# find the children on the rig (the camera name) for child in obj.children:
active_cam = ob.children[0].name if child.type == 'CAMERA':
# cam = bpy.data.cameras[bpy.data.objects[active_cam]] cam = child
scene_cam = bpy.context.scene.camera 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"}):
return obj.parent, obj
return None, None
class CameraRigMixin():
@classmethod
def poll(cls, context):
if context.active_object is not None:
return get_arm_and_cam(context.active_object) != (None, None)
if active_cam != scene_cam.name: return False
bpy.context.scene.camera = bpy.data.objects[active_cam]
else:
return None
class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator): class ADD_CAMERA_RIGS_OT_set_scene_camera(Operator, CameraRigMixin):
bl_idname = "add_camera_rigs.set_scene_camera" bl_idname = "add_camera_rigs.set_scene_camera"
bl_label = "Make Camera Active" bl_label = "Make Camera Active"
bl_description = "Makes the camera parented to this rig the active scene camera" bl_description = "Makes the camera parented to this rig the active scene camera"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context): def execute(self, context):
set_scene_camera() arm, cam = get_arm_and_cam(context.active_object)
scene_cam = context.scene.camera
return {'FINISHED'} if cam is not None and cam is not scene_cam:
context.scene.camera = cam
return {'FINISHED'}
return {'CANCELLED'}
def markerBind():
'''Defines the function to add a marker to timeling and bind camera''' class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator, CameraRigMixin):
rig = bpy.context.active_object # rig object
active_cam = rig.children[0] # camera object
# switch area to DOPESHEET to add marker
bpy.context.area.type = 'DOPESHEET_EDITOR'
# add marker
bpy.ops.marker.add() # it will automatiically have the name of the camera
# select rig camera
bpy.context.view_layer.objects.active = active_cam
# bind marker to selected camera
bpy.ops.marker.camera_bind()
# make the rig the active object before finishing
bpy.context.view_layer.objects.active = rig
bpy.data.objects[active_cam.name].select_set(False)
bpy.data.objects[rig.name].select_set(True)
# switch back to 3d view
bpy.context.area.type = 'VIEW_3D'
class ADD_CAMERA_RIGS_OT_add_marker_bind(Operator):
bl_idname = "add_camera_rigs.add_marker_bind" bl_idname = "add_camera_rigs.add_marker_bind"
bl_label = "Add Marker and Bind Camera" bl_label = "Add Marker and Bind Camera"
bl_description = "Add marker to current frame then bind rig camera to it (for camera switching)" bl_description = "Add marker to current frame then bind rig camera to it (for camera switching)"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context): def execute(self, context):
markerBind() arm, cam = get_arm_and_cam(context.active_object)
return {'FINISHED'}
marker = context.scene.timeline_markers.new(
"cam_" + str(context.scene.frame_current),
frame=context.scene.frame_current
)
marker.camera = cam
def add_DOF_object(): return {'FINISHED'}
"""Define the function to add an Empty as DOF object """
smode = bpy.context.mode
rig = bpy.context.active_object
bone = rig.data.bones['aim_MCH']
active_cam = rig.children[0].name
cam = bpy.data.cameras[bpy.data.objects[active_cam].data.name]
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
# Add Empty
bpy.ops.object.empty_add()
obj = bpy.context.active_object
obj.name = "Empty_DOF"
# parent to aim_MCH
obj.parent = rig
obj.parent_type = "BONE"
obj.parent_bone = "aim_MCH"
# clear loc and rot
bpy.ops.object.location_clear()
bpy.ops.object.rotation_clear()
# move to bone head
obj.location = bone.head
# make this new empty the dof_object
cam.dof.focus_object = obj
# make the rig the active object before finishing
bpy.context.view_layer.objects.active = rig
bpy.data.objects[obj.name].select_set(False)
bpy.data.objects[rig.name].select_set(True)
bpy.ops.object.mode_set(mode=smode, toggle=False)
class ADD_CAMERA_RIGS_OT_add_dof_object(Operator): class ADD_CAMERA_RIGS_OT_add_dof_object(Operator, CameraRigMixin):
bl_idname = "add_camera_rigs.add_dof_object" bl_idname = "add_camera_rigs.add_dof_object"
bl_label = "Add DOF Object" bl_label = "Add DOF Object"
bl_description = "Create Empty and add as DOF Object" bl_description = "Create Empty and add as DOF Object"
@classmethod
def poll(cls, context):
return context.active_object is not None
def execute(self, context): def execute(self, context):
add_DOF_object() arm, cam = get_arm_and_cam(context.active_object)
bone = arm.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_type = "BONE"
empty_obj.parent_bone = "Aim_shape_rotation-MCH"
# Move to bone head
empty_obj.location = bone.head
# Make this new empty the dof_object
cam.data.dof.use_dof = True
cam.data.dof.focus_object = empty_obj
return {'FINISHED'} return {'FINISHED'}
......
import bpy # ##### 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 #####
from bpy.types import AddonPreferences from bpy.types import AddonPreferences
from bpy.props import StringProperty from bpy.props import StringProperty
class Add_Camera_Rigs_Preferences(AddonPreferences): class AddCameraRigsPreferences(AddonPreferences):
bl_idname = 'add_camera_rigs' bl_idname = 'add_camera_rigs'
# widget prefix # Widget prefix
widget_prefix: StringProperty( widget_prefix: StringProperty(
name="Camera Widget prefix", name="Camera Widget prefix",
description="Choose a prefix for the widget objects", description="Prefix for the widget objects",
default="WDGT_", default="WGT-",
) )
# collection name # Collection name
camera_widget_collection_name: StringProperty( camera_widget_collection_name: StringProperty(
name="Bone Widget collection name", name="Bone Widget collection name",
description="Choose a name for the collection the widgets will appear", description="Name for the collection the widgets will appear",
default="WDGTS_camera", default="Widgets",
) )
def draw(self, context): def draw(self, context):
...@@ -30,7 +47,7 @@ class Add_Camera_Rigs_Preferences(AddonPreferences): ...@@ -30,7 +47,7 @@ class Add_Camera_Rigs_Preferences(AddonPreferences):
classes = ( classes = (
Add_Camera_Rigs_Preferences, AddCameraRigsPreferences,
) )
......
# ##### 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 import bpy
from bpy.types import Panel from bpy.types import Panel
from .operators import get_arm_and_cam, CameraRigMixin
class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel): class ADD_CAMERA_RIGS_PT_camera_rig_ui(Panel, CameraRigMixin):
bl_category = 'Create' bl_label = "Camera Rig"
bl_label = "Camera Rig UI"
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type = 'UI'
bl_category = 'Item'
_ACTIVE_OBJECT: object = None
_ACTIVE_RIG_TYPE: str = None
@classmethod
def poll(self, context):
self._ACTIVE_OBJECT = bpy.context.active_object
if self._ACTIVE_OBJECT != None and "rig_id" in self._ACTIVE_OBJECT:
rigType = self._ACTIVE_OBJECT["rig_id"]
if rigType == "Dolly_rig" or rigType == "Crane_rig":
self._ACTIVE_RIG_TYPE = rigType
return True
return False
def draw(self, context): def draw(self, context):
arm = self._ACTIVE_OBJECT.data active_object = context.active_object
poseBones = self._ACTIVE_OBJECT.pose.bones arm, cam = get_arm_and_cam(context.active_object)
activeCameraName = self._ACTIVE_OBJECT.children[0].name pose_bones = arm.pose.bones
cam_data = cam.data
cam = bpy.data.cameras[bpy.data.objects[activeCameraName].data.name]
layout = self.layout.box().column() layout = self.layout.box().column()
layout.label(text="Clipping:") layout.label(text="Clipping:")
layout.prop(cam, "clip_start", text="Start") layout.prop(cam_data, "clip_start", text="Start")
layout.prop(cam, "clip_end", text="End") layout.prop(cam_data, "clip_end", text="End")
layout.prop(cam, "type") layout.prop(cam_data, "type")
layout.prop(cam.dof, "use_dof") layout.prop(cam_data.dof, "use_dof")
if cam.dof.use_dof: if cam_data.dof.use_dof:
if cam.dof.focus_object is None: if cam_data.dof.focus_object is None:
layout.operator("add_camera_rigs.add_dof_object", text="Add DOF Empty") layout.operator("add_camera_rigs.add_dof_object",
layout.prop(poseBones["Camera"], '["focus_distance"]', text="Focus Distance") text="Add DOF Empty", icon="OUTLINER_OB_EMPTY")
layout.prop(poseBones["Camera"], '["f-stop"]', text="F-Stop") layout.prop(pose_bones["Camera"],
'["focus_distance"]', text="Focus Distance")
layout.prop(self._ACTIVE_OBJECT, 'show_in_front', toggle=False, text='Show in front') layout.prop(pose_bones["Camera"],
layout.prop(cam, "show_limits") '["aperture_fstop"]', text="F-Stop")
layout.prop(cam, "show_passepartout")
if cam.show_passepartout: layout.prop(active_object, 'show_in_front',
layout.prop(cam, "passepartout_alpha") toggle=False, text='Show in Front')
layout.prop(cam_data, "show_limits")
layout.prop(cam_data, "show_passepartout")
if cam_data.show_passepartout:
layout.prop(cam_data, "passepartout_alpha")
layout.row().separator() layout.row().separator()
# added the comp guides here # Added the comp guides here
layout.operator( layout.popover(
"wm.call_menu", text="Composition Guides").name = "ADD_CAMERA_RIGS_MT_composition_guides_menu" panel="ADD_CAMERA_RIGS_PT_composition_guides",
text="Composition Guides",)
layout.row().separator() layout.row().separator()
layout.prop(bpy.data.objects[activeCameraName], layout.prop(cam,
"hide_select", text="Make Camera Unselectable") "hide_select", text="Make Camera Unselectable")
layout.operator("add_camera_rigs.add_marker_bind", layout.operator("add_camera_rigs.add_marker_bind",
text="Add Marker and Bind") text="Add Marker and Bind", icon="MARKER_HLT")
if bpy.context.scene.camera.name != activeCameraName: if context.scene.camera is not cam:
layout.operator("add_camera_rigs.set_scene_camera", layout.operator("add_camera_rigs.set_scene_camera",
text="Make Camera Active", icon='CAMERA_DATA') text="Make Camera Active", icon='CAMERA_DATA')
# Camera Lens
layout.label(text="Focal Length:")
layout.prop(poseBones["Camera"], '["focal_length"]', text="Focal Length (mm)")
if self._ACTIVE_RIG_TYPE == "Crane_rig": # Camera lens
layout = layout.box().column() layout.separator()
layout.prop(pose_bones["Camera"], '["lens"]', text="Focal Length (mm)")
# Crane arm stuff
layout.label(text="Crane Arm:")
layout.prop(poseBones["Crane_height"], 'scale', index=1, text="Arm Height")
layout.prop(poseBones["Crane_arm"], 'scale', index=1, text="Arm Length")
# Track to Constraint # Track to Constraint
layout.label(text="Tracking:") layout.label(text="Tracking:")
layout.prop(poseBones["Camera"], '["lock"]', text="Aim Lock", slider=True) 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()
# 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")
def register(): def register():
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment