1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 from bpy
.types
import Operator
21 from bpy_extras
import object_utils
22 from mathutils
import Vector
23 from rna_prop_ui
import rna_idprop_ui_prop_get
26 from .create_widgets
import (create_root_widget
,
27 create_camera_widget
, create_camera_offset_widget
,
28 create_aim_widget
, create_circle_widget
,
32 def create_prop_driver(rig
, cam
, prop_from
, prop_to
):
33 """Create driver to a property on the rig"""
34 driver
= cam
.data
.driver_add(prop_to
)
35 driver
.driver
.type = 'SCRIPTED'
36 var
= driver
.driver
.variables
.new()
38 var
.type = 'SINGLE_PROP'
40 # Target the custom bone property
41 var
.targets
[0].id = rig
42 var
.targets
[0].data_path
= 'pose.bones["Camera"]["%s"]' % prop_from
43 driver
.driver
.expression
= 'var'
46 def create_dolly_bones(rig
):
47 """Create bones for the dolly camera rig"""
48 bones
= rig
.data
.edit_bones
51 root
= bones
.new("Root")
52 root
.tail
= (0.0, 1.0, 0.0)
55 ctrl_aim_child
= bones
.new("Aim_shape_rotation-MCH")
56 ctrl_aim_child
.head
= (0.0, 10.0, 1.7)
57 ctrl_aim_child
.tail
= (0.0, 11.0, 1.7)
58 ctrl_aim_child
.layers
= tuple(i
== 1 for i
in range(32))
60 ctrl_aim
= bones
.new("Aim")
61 ctrl_aim
.head
= (0.0, 10.0, 1.7)
62 ctrl_aim
.tail
= (0.0, 11.0, 1.7)
63 ctrl_aim
.show_wire
= True
65 ctrl
= bones
.new("Camera")
66 ctrl
.head
= (0.0, 0.0, 1.7)
67 ctrl
.tail
= (0.0, 1.0, 1.7)
70 ctrl_offset
= bones
.new("Camera_offset")
71 ctrl_offset
.head
= (0.0, 0.0, 1.7)
72 ctrl_offset
.tail
= (0.0, 1.0, 1.7)
73 ctrl_offset
.show_wire
= True
77 ctrl_offset
.parent
= ctrl
78 ctrl_aim
.parent
= root
79 ctrl_aim_child
.parent
= ctrl_aim
81 # Jump into object mode
82 bpy
.ops
.object.mode_set(mode
='OBJECT')
83 pose_bones
= rig
.pose
.bones
84 # Lock the relevant scale channels of the Camera_offset bone
85 pose_bones
["Camera_offset"].lock_scale
= (True,) * 3
88 def create_crane_bones(rig
):
89 """Create bones for the crane camera rig"""
90 bones
= rig
.data
.edit_bones
93 root
= bones
.new("Root")
94 root
.tail
= (0.0, 1.0, 0.0)
97 ctrl_aim_child
= bones
.new("Aim_shape_rotation-MCH")
98 ctrl_aim_child
.head
= (0.0, 10.0, 1.7)
99 ctrl_aim_child
.tail
= (0.0, 11.0, 1.7)
100 ctrl_aim_child
.layers
= tuple(i
== 1 for i
in range(32))
102 ctrl_aim
= bones
.new("Aim")
103 ctrl_aim
.head
= (0.0, 10.0, 1.7)
104 ctrl_aim
.tail
= (0.0, 11.0, 1.7)
105 ctrl_aim
.show_wire
= True
107 ctrl
= bones
.new("Camera")
108 ctrl
.head
= (0.0, 1.0, 1.7)
109 ctrl
.tail
= (0.0, 2.0, 1.7)
111 ctrl_offset
= bones
.new("Camera_offset")
112 ctrl_offset
.head
= (0.0, 1.0, 1.7)
113 ctrl_offset
.tail
= (0.0, 2.0, 1.7)
115 arm
= bones
.new("Crane_arm")
116 arm
.head
= (0.0, 0.0, 1.7)
117 arm
.tail
= (0.0, 1.0, 1.7)
119 height
= bones
.new("Crane_height")
120 height
.head
= (0.0, 0.0, 0.0)
121 height
.tail
= (0.0, 0.0, 1.7)
125 ctrl_offset
.parent
= ctrl
126 ctrl
.use_inherit_rotation
= False
127 ctrl
.use_inherit_scale
= False
128 ctrl
.show_wire
= True
131 arm
.use_inherit_scale
= False
134 ctrl_aim
.parent
= root
135 ctrl_aim_child
.parent
= ctrl_aim
137 # Jump into object mode
138 bpy
.ops
.object.mode_set(mode
='OBJECT')
139 pose_bones
= rig
.pose
.bones
141 # Lock the relevant loc, rot and scale
142 pose_bones
["Crane_arm"].lock_rotation
= (False, True, False)
143 pose_bones
["Crane_arm"].lock_scale
= (True, False, True)
144 pose_bones
["Crane_height"].lock_location
= (True,) * 3
145 pose_bones
["Crane_height"].lock_rotation
= (True,) * 3
146 pose_bones
["Crane_height"].lock_scale
= (True, False, True)
147 pose_bones
["Camera_offset"].lock_scale
= (True,) * 3
150 def setup_3d_rig(rig
, cam
):
151 """Finish setting up Dolly and Crane rigs"""
152 # Jump into object mode and change bones to euler
153 bpy
.ops
.object.mode_set(mode
='OBJECT')
154 pose_bones
= rig
.pose
.bones
155 for bone
in pose_bones
:
156 bone
.rotation_mode
= 'XYZ'
159 pb
= pose_bones
['Camera']
161 prop
= rna_idprop_ui_prop_get(pb
, "lens", create
=True)
162 prop
["default"] = 50.0
164 prop
["max"] = 1000000.0
165 prop
["soft_max"] = 5000.0
168 root_widget
= create_root_widget("Camera_Root")
169 camera_widget
= create_camera_widget("Camera")
170 camera_offset_widget
= create_camera_offset_widget("Camera_offset")
171 aim_widget
= create_aim_widget("Aim")
173 # Add the custom bone shapes
174 pose_bones
["Root"].custom_shape
= root_widget
175 pose_bones
["Aim"].custom_shape
= aim_widget
176 pose_bones
["Camera"].custom_shape
= camera_widget
177 pose_bones
["Camera_offset"].custom_shape
= camera_offset_widget
179 # Set the "Override Transform" field to the mechanism position
180 pose_bones
["Aim"].custom_shape_transform
= pose_bones
["Aim_shape_rotation-MCH"]
182 # Add constraints to bones
183 con
= pose_bones
['Aim_shape_rotation-MCH'].constraints
.new('COPY_ROTATION')
185 con
.subtarget
= "Camera"
187 con
= pose_bones
['Camera'].constraints
.new('TRACK_TO')
188 con
.track_axis
= 'TRACK_Y'
191 con
.subtarget
= "Aim"
192 con
.use_target_z
= True
194 cam
.data
.display_size
= 1.0
195 cam
.rotation_euler
[0] = pi
/ 2.0 # Rotate the camera 90 degrees in x
197 create_prop_driver(rig
, cam
, "lens", "lens")
200 def create_2d_bones(context
, rig
, cam
):
201 """Create bones for the 2D camera rig"""
202 scene
= context
.scene
203 bones
= rig
.data
.edit_bones
206 bones
= rig
.data
.edit_bones
207 root
= bones
.new("Root")
208 root
.tail
= Vector((0.0, 0.0, 1.0))
209 root
.show_wire
= True
211 ctrl
= bones
.new('Camera')
212 ctrl
.tail
= Vector((0.0, 0.0, 1.0))
213 ctrl
.show_wire
= True
215 left_corner
= bones
.new("Left_corner")
216 left_corner
.head
= (-3, 10, -2)
217 left_corner
.tail
= left_corner
.head
+ Vector((0.0, 0.0, 1.0))
218 left_corner
.show_wire
= True
220 right_corner
= bones
.new("Right_corner")
221 right_corner
.head
= (3, 10, -2)
222 right_corner
.tail
= right_corner
.head
+ Vector((0.0, 0.0, 1.0))
223 right_corner
.show_wire
= True
225 corner_distance_x
= (left_corner
.head
- right_corner
.head
).length
226 corner_distance_y
= -left_corner
.head
.z
227 corner_distance_z
= left_corner
.head
.y
229 center
= bones
.new("Center-MCH")
230 center
.head
= ((right_corner
.head
+ left_corner
.head
) / 2.0)
231 center
.tail
= center
.head
+ Vector((0.0, 0.0, 1.0))
232 center
.layers
= tuple(i
== 1 for i
in range(32))
233 center
.show_wire
= True
237 left_corner
.parent
= root
238 right_corner
.parent
= root
241 # Jump into object mode and change bones to euler
242 bpy
.ops
.object.mode_set(mode
='OBJECT')
243 pose_bones
= rig
.pose
.bones
244 for bone
in pose_bones
:
245 bone
.rotation_mode
= 'XYZ'
248 center_drivers
= pose_bones
["Center-MCH"].driver_add("location")
251 driver
= center_drivers
[0].driver
252 driver
.type = 'AVERAGE'
254 for corner
in ('left', 'right'):
255 var
= driver
.variables
.new()
257 var
.type = 'TRANSFORMS'
258 var
.targets
[0].id = rig
259 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
260 var
.targets
[0].transform_type
= 'LOC_X'
261 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
264 driver
= center_drivers
[1].driver
265 driver
.type = 'SCRIPTED'
267 driver
.expression
= '({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + (left_y + right_y)/2'.format(
268 distance_x
=corner_distance_x
)
270 for direction
in ('x', 'y'):
271 for corner
in ('left', 'right'):
272 var
= driver
.variables
.new()
273 var
.name
= '%s_%s' % (corner
, direction
)
274 var
.type = 'TRANSFORMS'
275 var
.targets
[0].id = rig
276 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
277 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
278 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
280 var
= driver
.variables
.new()
281 var
.name
= 'res_' + direction
282 var
.type = 'SINGLE_PROP'
283 var
.targets
[0].id_type
= 'SCENE'
284 var
.targets
[0].id = scene
285 var
.targets
[0].data_path
= 'render.resolution_' + direction
288 driver
= center_drivers
[2].driver
289 driver
.type = 'AVERAGE'
291 for corner
in ('left', 'right'):
292 var
= driver
.variables
.new()
294 var
.type = 'TRANSFORMS'
295 var
.targets
[0].id = rig
296 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
297 var
.targets
[0].transform_type
= 'LOC_Z'
298 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
301 con
= pose_bones
["Camera"].constraints
.new('DAMPED_TRACK')
303 con
.subtarget
= "Center-MCH"
304 con
.track_axis
= 'TRACK_NEGATIVE_Z'
307 left_widget
= create_corner_widget("Left_corner", reverse
=True)
308 right_widget
= create_corner_widget("Right_corner")
309 parent_widget
= create_circle_widget("Root", radius
=0.5)
310 camera_widget
= create_circle_widget("Camera_2D", radius
=0.3)
312 # Add the custom bone shapes
313 pose_bones
["Left_corner"].custom_shape
= left_widget
314 pose_bones
["Right_corner"].custom_shape
= right_widget
315 pose_bones
["Root"].custom_shape
= parent_widget
316 pose_bones
["Camera"].custom_shape
= camera_widget
318 # Lock the relevant loc, rot and scale
319 pose_bones
["Left_corner"].lock_rotation
= (True,) * 3
320 pose_bones
["Right_corner"].lock_rotation
= (True,) * 3
321 pose_bones
["Camera"].lock_rotation
= (True,) * 3
322 pose_bones
["Camera"].lock_scale
= (True,) * 3
326 cam
.data
.sensor_fit
= "HORIZONTAL" # Avoids distortion in portrait format
328 # Property to switch between rotation and switch mode
329 pose_bones
["Camera"]['rotation_shift'] = 0.0
330 prop
= rna_idprop_ui_prop_get(pose_bones
["Camera"], 'rotation_shift', create
=True)
333 prop
["soft_min"] = 0.0
334 prop
["soft_max"] = 1.0
335 prop
["description"] = 'rotation_shift'
337 # Rotation / shift switch driver
338 driver
= con
.driver_add('influence').driver
339 driver
.expression
= '1 - rotation_shift'
341 var
= driver
.variables
.new()
342 var
.name
= 'rotation_shift'
343 var
.type = 'SINGLE_PROP'
344 var
.targets
[0].id = rig
345 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
347 # Focal length driver
348 driver
= cam
.data
.driver_add('lens').driver
349 driver
.expression
= 'abs({distance_z} - (left_z + right_z)/2 + cam_z) * 36 / frame_width'.format(
350 distance_z
=corner_distance_z
)
352 var
= driver
.variables
.new()
353 var
.name
= 'frame_width'
354 var
.type = 'LOC_DIFF'
355 var
.targets
[0].id = rig
356 var
.targets
[0].bone_target
= "Left_corner"
357 var
.targets
[0].transform_space
= 'WORLD_SPACE'
358 var
.targets
[1].id = rig
359 var
.targets
[1].bone_target
= "Right_corner"
360 var
.targets
[1].transform_space
= 'WORLD_SPACE'
362 for corner
in ('left', 'right'):
363 var
= driver
.variables
.new()
364 var
.name
= corner
+ '_z'
365 var
.type = 'TRANSFORMS'
366 var
.targets
[0].id = rig
367 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
368 var
.targets
[0].transform_type
= 'LOC_Z'
369 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
371 var
= driver
.variables
.new()
373 var
.type = 'TRANSFORMS'
374 var
.targets
[0].id = rig
375 var
.targets
[0].bone_target
= "Camera"
376 var
.targets
[0].transform_type
= 'LOC_Z'
377 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
379 # Orthographic scale driver
380 driver
= cam
.data
.driver_add('ortho_scale').driver
381 driver
.expression
= 'abs({distance_x} - (left_x - right_x))'.format(distance_x
=corner_distance_x
)
383 for corner
in ('left', 'right'):
384 var
= driver
.variables
.new()
385 var
.name
= corner
+ '_x'
386 var
.type = 'TRANSFORMS'
387 var
.targets
[0].id = rig
388 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
389 var
.targets
[0].transform_type
= 'LOC_X'
390 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
393 driver
= cam
.data
.driver_add('shift_x').driver
395 driver
.expression
= 'rotation_shift * (((left_x + right_x)/2 - cam_x) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36)'.format(
396 distance_z
=corner_distance_z
)
398 var
= driver
.variables
.new()
399 var
.name
= 'rotation_shift'
400 var
.type = 'SINGLE_PROP'
401 var
.targets
[0].id = rig
402 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
404 for direction
in ('x', 'z'):
405 for corner
in ('left', 'right'):
406 var
= driver
.variables
.new()
407 var
.name
= '%s_%s' % (corner
, direction
)
408 var
.type = 'TRANSFORMS'
409 var
.targets
[0].id = rig
410 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
411 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
412 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
414 var
= driver
.variables
.new()
415 var
.name
= 'cam_' + direction
416 var
.type = 'TRANSFORMS'
417 var
.targets
[0].id = rig
418 var
.targets
[0].bone_target
= "Camera"
419 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
420 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
422 var
= driver
.variables
.new()
424 var
.type = 'SINGLE_PROP'
425 var
.targets
[0].id_type
= 'CAMERA'
426 var
.targets
[0].id = cam
.data
427 var
.targets
[0].data_path
= 'lens'
430 driver
= cam
.data
.driver_add('shift_y').driver
432 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(
433 distance_y
=corner_distance_y
, distance_z
=corner_distance_z
)
435 var
= driver
.variables
.new()
436 var
.name
= 'rotation_shift'
437 var
.type = 'SINGLE_PROP'
438 var
.targets
[0].id = rig
439 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
441 for direction
in ('y', 'z'):
442 for corner
in ('left', 'right'):
443 var
= driver
.variables
.new()
444 var
.name
= '%s_%s' % (corner
, direction
)
445 var
.type = 'TRANSFORMS'
446 var
.targets
[0].id = rig
447 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
448 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
449 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
451 var
= driver
.variables
.new()
452 var
.name
= 'cam_' + direction
453 var
.type = 'TRANSFORMS'
454 var
.targets
[0].id = rig
455 var
.targets
[0].bone_target
= "Camera"
456 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
457 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
459 for direction
in ('x', 'y'):
460 var
= driver
.variables
.new()
461 var
.name
= 'res_' + direction
462 var
.type = 'SINGLE_PROP'
463 var
.targets
[0].id_type
= 'SCENE'
464 var
.targets
[0].id = scene
465 var
.targets
[0].data_path
= 'render.resolution_' + direction
467 var
= driver
.variables
.new()
469 var
.type = 'SINGLE_PROP'
470 var
.targets
[0].id_type
= 'CAMERA'
471 var
.targets
[0].id = cam
.data
472 var
.targets
[0].data_path
= 'lens'
475 def build_camera_rig(context
, mode
):
476 """Create stuff common to all camera rigs."""
477 # Add the camera object
478 cam_name
= "%s_Camera" % mode
.capitalize()
479 cam_data
= bpy
.data
.cameras
.new(cam_name
)
480 cam
= object_utils
.object_data_add(context
, cam_data
, name
=cam_name
)
481 context
.scene
.camera
= cam
484 rig_name
= mode
.capitalize() + "_Rig"
485 rig_data
= bpy
.data
.armatures
.new(rig_name
)
486 rig
= object_utils
.object_data_add(context
, rig_data
, name
=rig_name
)
487 rig
["rig_id"] = "%s" % rig_name
488 rig
.location
= context
.scene
.cursor
.location
490 bpy
.ops
.object.mode_set(mode
='EDIT')
492 # Add new bones and setup specific rigs
494 create_dolly_bones(rig
)
495 setup_3d_rig(rig
, cam
)
496 elif mode
== "CRANE":
497 create_crane_bones(rig
)
498 setup_3d_rig(rig
, cam
)
500 create_2d_bones(context
, rig
, cam
)
502 # Parent the camera to the rig
503 cam
.location
= (0.0, -1.0, 0.0) # Move the camera to the correct position
505 cam
.parent_type
= "BONE"
507 cam
.parent_bone
= "Camera"
509 cam
.parent_bone
= "Camera_offset"
511 # Change display to BBone: it just looks nicer
512 rig
.data
.display_type
= 'BBONE'
513 # Change display to wire for object
514 rig
.display_type
= 'WIRE'
516 # Lock camera transforms
517 cam
.lock_location
= (True,) * 3
518 cam
.lock_rotation
= (True,) * 3
519 cam
.lock_scale
= (True,) * 3
521 # Add custom properties to the armature’s Camera bone,
522 # so that all properties may be animated in a single action
524 pose_bones
= rig
.pose
.bones
526 # DOF Focus Distance property
527 pb
= pose_bones
['Camera']
528 pb
["focus_distance"] = 10.0
529 prop
= rna_idprop_ui_prop_get(pb
, "focus_distance", create
=True)
530 prop
["default"] = 10.0
533 # DOF F-Stop property
534 pb
= pose_bones
['Camera']
535 pb
["aperture_fstop"] = 2.8
536 prop
= rna_idprop_ui_prop_get(pb
, "aperture_fstop", create
=True)
537 prop
["default"] = 2.8
539 prop
["soft_min"] = 0.1
540 prop
["soft_max"] = 128.0
542 # Add drivers to link the camera properties to the custom props
544 create_prop_driver(rig
, cam
, "focus_distance", "dof.focus_distance")
545 create_prop_driver(rig
, cam
, "aperture_fstop", "dof.aperture_fstop")
547 # Make the rig the active object
548 view_layer
= context
.view_layer
549 for obj
in view_layer
.objects
:
550 obj
.select_set(False)
552 view_layer
.objects
.active
= rig
555 class OBJECT_OT_build_camera_rig(Operator
):
556 bl_idname
= "object.build_camera_rig"
557 bl_label
= "Build Camera Rig"
558 bl_description
= "Build a Camera Rig"
559 bl_options
= {'REGISTER', 'UNDO'}
561 mode
: bpy
.props
.EnumProperty(items
=(('DOLLY', 'Dolly', 'Dolly rig'),
562 ('CRANE', 'Crane', 'Crane rig',),
563 ('2D', '2D', '2D rig')),
565 description
="Type of camera to create",
568 def execute(self
, context
):
570 build_camera_rig(context
, self
.mode
)
574 def add_dolly_crane_buttons(self
, context
):
575 """Dolly and crane entries in the Add Object > Camera Menu"""
576 if context
.mode
== 'OBJECT':
577 self
.layout
.operator(
578 OBJECT_OT_build_camera_rig
.bl_idname
,
579 text
="Dolly Camera Rig",
583 self
.layout
.operator(
584 OBJECT_OT_build_camera_rig
.bl_idname
,
585 text
="Crane Camera Rig",
589 self
.layout
.operator(
590 OBJECT_OT_build_camera_rig
.bl_idname
,
591 text
="2D Camera Rig",
592 icon
='PIVOT_BOUNDBOX'
597 OBJECT_OT_build_camera_rig
,
602 from bpy
.utils
import register_class
606 bpy
.types
.VIEW3D_MT_camera_add
.append(add_dolly_crane_buttons
)
610 from bpy
.utils
import unregister_class
612 unregister_class(cls
)
614 bpy
.types
.VIEW3D_MT_camera_add
.remove(add_dolly_crane_buttons
)
617 if __name__
== "__main__":