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
25 from .create_widgets
import (create_root_widget
,
26 create_camera_widget
, create_camera_offset_widget
,
27 create_aim_widget
, create_circle_widget
,
31 def create_prop_driver(rig
, cam
, prop_from
, prop_to
):
32 """Create driver to a property on the rig"""
33 driver
= cam
.data
.driver_add(prop_to
)
34 driver
.driver
.type = 'SCRIPTED'
35 var
= driver
.driver
.variables
.new()
37 var
.type = 'SINGLE_PROP'
39 # Target the custom bone property
40 var
.targets
[0].id = rig
41 var
.targets
[0].data_path
= 'pose.bones["Camera"]["%s"]' % prop_from
42 driver
.driver
.expression
= 'var'
45 def create_dolly_bones(rig
):
46 """Create bones for the dolly camera rig"""
47 bones
= rig
.data
.edit_bones
50 root
= bones
.new("Root")
51 root
.tail
= (0.0, 1.0, 0.0)
54 ctrl_aim_child
= bones
.new("Aim_shape_rotation-MCH")
55 ctrl_aim_child
.head
= (0.0, 10.0, 1.7)
56 ctrl_aim_child
.tail
= (0.0, 11.0, 1.7)
57 ctrl_aim_child
.layers
= tuple(i
== 1 for i
in range(32))
59 ctrl_aim
= bones
.new("Aim")
60 ctrl_aim
.head
= (0.0, 10.0, 1.7)
61 ctrl_aim
.tail
= (0.0, 11.0, 1.7)
62 ctrl_aim
.show_wire
= True
64 ctrl
= bones
.new("Camera")
65 ctrl
.head
= (0.0, 0.0, 1.7)
66 ctrl
.tail
= (0.0, 1.0, 1.7)
69 ctrl_offset
= bones
.new("Camera_offset")
70 ctrl_offset
.head
= (0.0, 0.0, 1.7)
71 ctrl_offset
.tail
= (0.0, 1.0, 1.7)
72 ctrl_offset
.show_wire
= True
76 ctrl_offset
.parent
= ctrl
77 ctrl_aim
.parent
= root
78 ctrl_aim_child
.parent
= ctrl_aim
80 # Jump into object mode
81 bpy
.ops
.object.mode_set(mode
='OBJECT')
82 pose_bones
= rig
.pose
.bones
83 # Lock the relevant scale channels of the Camera_offset bone
84 pose_bones
["Camera_offset"].lock_scale
= (True,) * 3
87 def create_crane_bones(rig
):
88 """Create bones for the crane camera rig"""
89 bones
= rig
.data
.edit_bones
92 root
= bones
.new("Root")
93 root
.tail
= (0.0, 1.0, 0.0)
96 ctrl_aim_child
= bones
.new("Aim_shape_rotation-MCH")
97 ctrl_aim_child
.head
= (0.0, 10.0, 1.7)
98 ctrl_aim_child
.tail
= (0.0, 11.0, 1.7)
99 ctrl_aim_child
.layers
= tuple(i
== 1 for i
in range(32))
101 ctrl_aim
= bones
.new("Aim")
102 ctrl_aim
.head
= (0.0, 10.0, 1.7)
103 ctrl_aim
.tail
= (0.0, 11.0, 1.7)
104 ctrl_aim
.show_wire
= True
106 ctrl
= bones
.new("Camera")
107 ctrl
.head
= (0.0, 1.0, 1.7)
108 ctrl
.tail
= (0.0, 2.0, 1.7)
110 ctrl_offset
= bones
.new("Camera_offset")
111 ctrl_offset
.head
= (0.0, 1.0, 1.7)
112 ctrl_offset
.tail
= (0.0, 2.0, 1.7)
114 arm
= bones
.new("Crane_arm")
115 arm
.head
= (0.0, 0.0, 1.7)
116 arm
.tail
= (0.0, 1.0, 1.7)
118 height
= bones
.new("Crane_height")
119 height
.head
= (0.0, 0.0, 0.0)
120 height
.tail
= (0.0, 0.0, 1.7)
124 ctrl_offset
.parent
= ctrl
125 ctrl
.use_inherit_rotation
= False
126 ctrl
.use_inherit_scale
= False
127 ctrl
.show_wire
= True
130 arm
.use_inherit_scale
= False
133 ctrl_aim
.parent
= root
134 ctrl_aim_child
.parent
= ctrl_aim
136 # Jump into object mode
137 bpy
.ops
.object.mode_set(mode
='OBJECT')
138 pose_bones
= rig
.pose
.bones
140 # Lock the relevant loc, rot and scale
141 pose_bones
["Crane_arm"].lock_rotation
= (False, True, False)
142 pose_bones
["Crane_arm"].lock_scale
= (True, False, True)
143 pose_bones
["Crane_height"].lock_location
= (True,) * 3
144 pose_bones
["Crane_height"].lock_rotation
= (True,) * 3
145 pose_bones
["Crane_height"].lock_scale
= (True, False, True)
146 pose_bones
["Camera_offset"].lock_scale
= (True,) * 3
149 def setup_3d_rig(rig
, cam
):
150 """Finish setting up Dolly and Crane rigs"""
151 # Jump into object mode and change bones to euler
152 bpy
.ops
.object.mode_set(mode
='OBJECT')
153 pose_bones
= rig
.pose
.bones
154 for bone
in pose_bones
:
155 bone
.rotation_mode
= 'XYZ'
158 pb
= pose_bones
['Camera']
160 ui_data
= pb
.id_properties_ui("lens")
161 ui_data
.update(min=1.0, max=1000000.0, soft_max
= 5000.0, default
=50.0)
164 root_widget
= create_root_widget("Camera_Root")
165 camera_widget
= create_camera_widget("Camera")
166 camera_offset_widget
= create_camera_offset_widget("Camera_offset")
167 aim_widget
= create_aim_widget("Aim")
169 # Add the custom bone shapes
170 pose_bones
["Root"].custom_shape
= root_widget
171 pose_bones
["Aim"].custom_shape
= aim_widget
172 pose_bones
["Camera"].custom_shape
= camera_widget
173 pose_bones
["Camera_offset"].custom_shape
= camera_offset_widget
175 # Set the "Override Transform" field to the mechanism position
176 pose_bones
["Aim"].custom_shape_transform
= pose_bones
["Aim_shape_rotation-MCH"]
178 # Add constraints to bones
179 con
= pose_bones
['Aim_shape_rotation-MCH'].constraints
.new('COPY_ROTATION')
181 con
.subtarget
= "Camera"
183 con
= pose_bones
['Camera'].constraints
.new('TRACK_TO')
184 con
.track_axis
= 'TRACK_Y'
187 con
.subtarget
= "Aim"
188 con
.use_target_z
= True
190 cam
.data
.display_size
= 1.0
191 cam
.rotation_euler
[0] = pi
/ 2.0 # Rotate the camera 90 degrees in x
193 create_prop_driver(rig
, cam
, "lens", "lens")
196 def create_2d_bones(context
, rig
, cam
):
197 """Create bones for the 2D camera rig"""
198 scene
= context
.scene
199 bones
= rig
.data
.edit_bones
202 bones
= rig
.data
.edit_bones
203 root
= bones
.new("Root")
204 root
.tail
= Vector((0.0, 0.0, 1.0))
205 root
.show_wire
= True
207 ctrl
= bones
.new('Camera')
208 ctrl
.tail
= Vector((0.0, 0.0, 1.0))
209 ctrl
.show_wire
= True
211 left_corner
= bones
.new("Left_corner")
212 left_corner
.head
= (-3, 10, -2)
213 left_corner
.tail
= left_corner
.head
+ Vector((0.0, 0.0, 1.0))
214 left_corner
.show_wire
= True
216 right_corner
= bones
.new("Right_corner")
217 right_corner
.head
= (3, 10, -2)
218 right_corner
.tail
= right_corner
.head
+ Vector((0.0, 0.0, 1.0))
219 right_corner
.show_wire
= True
221 corner_distance_x
= (left_corner
.head
- right_corner
.head
).length
222 corner_distance_y
= -left_corner
.head
.z
223 corner_distance_z
= left_corner
.head
.y
225 center
= bones
.new("Center-MCH")
226 center
.head
= ((right_corner
.head
+ left_corner
.head
) / 2.0)
227 center
.tail
= center
.head
+ Vector((0.0, 0.0, 1.0))
228 center
.layers
= tuple(i
== 1 for i
in range(32))
229 center
.show_wire
= True
233 left_corner
.parent
= root
234 right_corner
.parent
= root
237 # Jump into object mode and change bones to euler
238 bpy
.ops
.object.mode_set(mode
='OBJECT')
239 pose_bones
= rig
.pose
.bones
240 for bone
in pose_bones
:
241 bone
.rotation_mode
= 'XYZ'
244 center_drivers
= pose_bones
["Center-MCH"].driver_add("location")
247 driver
= center_drivers
[0].driver
248 driver
.type = 'AVERAGE'
250 for corner
in ('left', 'right'):
251 var
= driver
.variables
.new()
253 var
.type = 'TRANSFORMS'
254 var
.targets
[0].id = rig
255 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
256 var
.targets
[0].transform_type
= 'LOC_X'
257 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
260 driver
= center_drivers
[1].driver
261 driver
.type = 'SCRIPTED'
263 driver
.expression
= '({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + (left_y + right_y)/2'.format(
264 distance_x
=corner_distance_x
)
266 for direction
in ('x', 'y'):
267 for corner
in ('left', 'right'):
268 var
= driver
.variables
.new()
269 var
.name
= '%s_%s' % (corner
, direction
)
270 var
.type = 'TRANSFORMS'
271 var
.targets
[0].id = rig
272 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
273 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
274 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
276 var
= driver
.variables
.new()
277 var
.name
= 'res_' + direction
278 var
.type = 'SINGLE_PROP'
279 var
.targets
[0].id_type
= 'SCENE'
280 var
.targets
[0].id = scene
281 var
.targets
[0].data_path
= 'render.resolution_' + direction
284 driver
= center_drivers
[2].driver
285 driver
.type = 'AVERAGE'
287 for corner
in ('left', 'right'):
288 var
= driver
.variables
.new()
290 var
.type = 'TRANSFORMS'
291 var
.targets
[0].id = rig
292 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
293 var
.targets
[0].transform_type
= 'LOC_Z'
294 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
297 con
= pose_bones
["Camera"].constraints
.new('DAMPED_TRACK')
299 con
.subtarget
= "Center-MCH"
300 con
.track_axis
= 'TRACK_NEGATIVE_Z'
303 left_widget
= create_corner_widget("Left_corner", reverse
=True)
304 right_widget
= create_corner_widget("Right_corner")
305 parent_widget
= create_circle_widget("Root", radius
=0.5)
306 camera_widget
= create_circle_widget("Camera_2D", radius
=0.3)
308 # Add the custom bone shapes
309 pose_bones
["Left_corner"].custom_shape
= left_widget
310 pose_bones
["Right_corner"].custom_shape
= right_widget
311 pose_bones
["Root"].custom_shape
= parent_widget
312 pose_bones
["Camera"].custom_shape
= camera_widget
314 # Lock the relevant loc, rot and scale
315 pose_bones
["Left_corner"].lock_rotation
= (True,) * 3
316 pose_bones
["Right_corner"].lock_rotation
= (True,) * 3
317 pose_bones
["Camera"].lock_rotation
= (True,) * 3
318 pose_bones
["Camera"].lock_scale
= (True,) * 3
322 cam
.data
.sensor_fit
= "HORIZONTAL" # Avoids distortion in portrait format
324 # Property to switch between rotation and switch mode
325 pose_bones
["Camera"]['rotation_shift'] = 0.0
326 ui_data
= pose_bones
["Camera"].id_properties_ui('rotation_shift')
327 ui_data
.update(min=0.0, max=1.0, description
="rotation_shift")
329 # Rotation / shift switch driver
330 driver
= con
.driver_add('influence').driver
331 driver
.expression
= '1 - rotation_shift'
333 var
= driver
.variables
.new()
334 var
.name
= 'rotation_shift'
335 var
.type = 'SINGLE_PROP'
336 var
.targets
[0].id = rig
337 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
339 # Focal length driver
340 driver
= cam
.data
.driver_add('lens').driver
341 driver
.expression
= 'abs({distance_z} - (left_z + right_z)/2 + cam_z) * 36 / frame_width'.format(
342 distance_z
=corner_distance_z
)
344 var
= driver
.variables
.new()
345 var
.name
= 'frame_width'
346 var
.type = 'LOC_DIFF'
347 var
.targets
[0].id = rig
348 var
.targets
[0].bone_target
= "Left_corner"
349 var
.targets
[0].transform_space
= 'WORLD_SPACE'
350 var
.targets
[1].id = rig
351 var
.targets
[1].bone_target
= "Right_corner"
352 var
.targets
[1].transform_space
= 'WORLD_SPACE'
354 for corner
in ('left', 'right'):
355 var
= driver
.variables
.new()
356 var
.name
= corner
+ '_z'
357 var
.type = 'TRANSFORMS'
358 var
.targets
[0].id = rig
359 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
360 var
.targets
[0].transform_type
= 'LOC_Z'
361 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
363 var
= driver
.variables
.new()
365 var
.type = 'TRANSFORMS'
366 var
.targets
[0].id = rig
367 var
.targets
[0].bone_target
= "Camera"
368 var
.targets
[0].transform_type
= 'LOC_Z'
369 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
371 # Orthographic scale driver
372 driver
= cam
.data
.driver_add('ortho_scale').driver
373 driver
.expression
= 'abs({distance_x} - (left_x - right_x))'.format(distance_x
=corner_distance_x
)
375 for corner
in ('left', 'right'):
376 var
= driver
.variables
.new()
377 var
.name
= corner
+ '_x'
378 var
.type = 'TRANSFORMS'
379 var
.targets
[0].id = rig
380 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
381 var
.targets
[0].transform_type
= 'LOC_X'
382 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
385 driver
= cam
.data
.driver_add('shift_x').driver
387 driver
.expression
= 'rotation_shift * (((left_x + right_x)/2 - cam_x) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36)'.format(
388 distance_z
=corner_distance_z
)
390 var
= driver
.variables
.new()
391 var
.name
= 'rotation_shift'
392 var
.type = 'SINGLE_PROP'
393 var
.targets
[0].id = rig
394 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
396 for direction
in ('x', 'z'):
397 for corner
in ('left', 'right'):
398 var
= driver
.variables
.new()
399 var
.name
= '%s_%s' % (corner
, direction
)
400 var
.type = 'TRANSFORMS'
401 var
.targets
[0].id = rig
402 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
403 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
404 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
406 var
= driver
.variables
.new()
407 var
.name
= 'cam_' + direction
408 var
.type = 'TRANSFORMS'
409 var
.targets
[0].id = rig
410 var
.targets
[0].bone_target
= "Camera"
411 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
412 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
414 var
= driver
.variables
.new()
416 var
.type = 'SINGLE_PROP'
417 var
.targets
[0].id_type
= 'CAMERA'
418 var
.targets
[0].id = cam
.data
419 var
.targets
[0].data_path
= 'lens'
422 driver
= cam
.data
.driver_add('shift_y').driver
424 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(
425 distance_y
=corner_distance_y
, distance_z
=corner_distance_z
)
427 var
= driver
.variables
.new()
428 var
.name
= 'rotation_shift'
429 var
.type = 'SINGLE_PROP'
430 var
.targets
[0].id = rig
431 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
433 for direction
in ('y', 'z'):
434 for corner
in ('left', 'right'):
435 var
= driver
.variables
.new()
436 var
.name
= '%s_%s' % (corner
, direction
)
437 var
.type = 'TRANSFORMS'
438 var
.targets
[0].id = rig
439 var
.targets
[0].bone_target
= corner
.capitalize() + '_corner'
440 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
441 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
443 var
= driver
.variables
.new()
444 var
.name
= 'cam_' + direction
445 var
.type = 'TRANSFORMS'
446 var
.targets
[0].id = rig
447 var
.targets
[0].bone_target
= "Camera"
448 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
449 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
451 for direction
in ('x', 'y'):
452 var
= driver
.variables
.new()
453 var
.name
= 'res_' + direction
454 var
.type = 'SINGLE_PROP'
455 var
.targets
[0].id_type
= 'SCENE'
456 var
.targets
[0].id = scene
457 var
.targets
[0].data_path
= 'render.resolution_' + direction
459 var
= driver
.variables
.new()
461 var
.type = 'SINGLE_PROP'
462 var
.targets
[0].id_type
= 'CAMERA'
463 var
.targets
[0].id = cam
.data
464 var
.targets
[0].data_path
= 'lens'
467 def build_camera_rig(context
, mode
):
468 """Create stuff common to all camera rigs."""
469 # Add the camera object
470 cam_name
= "%s_Camera" % mode
.capitalize()
471 cam_data
= bpy
.data
.cameras
.new(cam_name
)
472 cam
= object_utils
.object_data_add(context
, cam_data
, name
=cam_name
)
473 context
.scene
.camera
= cam
476 rig_name
= mode
.capitalize() + "_Rig"
477 rig_data
= bpy
.data
.armatures
.new(rig_name
)
478 rig
= object_utils
.object_data_add(context
, rig_data
, name
=rig_name
)
479 rig
["rig_id"] = "%s" % rig_name
480 rig
.location
= context
.scene
.cursor
.location
482 bpy
.ops
.object.mode_set(mode
='EDIT')
484 # Add new bones and setup specific rigs
486 create_dolly_bones(rig
)
487 setup_3d_rig(rig
, cam
)
488 elif mode
== "CRANE":
489 create_crane_bones(rig
)
490 setup_3d_rig(rig
, cam
)
492 create_2d_bones(context
, rig
, cam
)
494 # Parent the camera to the rig
495 cam
.location
= (0.0, -1.0, 0.0) # Move the camera to the correct position
497 cam
.parent_type
= "BONE"
499 cam
.parent_bone
= "Camera"
501 cam
.parent_bone
= "Camera_offset"
503 # Change display to BBone: it just looks nicer
504 rig
.data
.display_type
= 'BBONE'
505 # Change display to wire for object
506 rig
.display_type
= 'WIRE'
508 # Lock camera transforms
509 cam
.lock_location
= (True,) * 3
510 cam
.lock_rotation
= (True,) * 3
511 cam
.lock_scale
= (True,) * 3
513 # Add custom properties to the armature’s Camera bone,
514 # so that all properties may be animated in a single action
516 pose_bones
= rig
.pose
.bones
518 # DOF Focus Distance property
519 pb
= pose_bones
['Camera']
520 pb
["focus_distance"] = 10.0
521 ui_data
= pb
.id_properties_ui('focus_distance')
522 ui_data
.update(min=0.0, default
=10.0)
524 # DOF F-Stop property
525 pb
= pose_bones
['Camera']
526 pb
["aperture_fstop"] = 2.8
527 ui_data
= pb
.id_properties_ui('aperture_fstop')
528 ui_data
.update(min=0.0, soft_min
=0.1, soft_max
=128.0, default
=2.8)
530 # Add drivers to link the camera properties to the custom props
532 create_prop_driver(rig
, cam
, "focus_distance", "dof.focus_distance")
533 create_prop_driver(rig
, cam
, "aperture_fstop", "dof.aperture_fstop")
535 # Make the rig the active object
536 view_layer
= context
.view_layer
537 for obj
in view_layer
.objects
:
538 obj
.select_set(False)
540 view_layer
.objects
.active
= rig
543 class OBJECT_OT_build_camera_rig(Operator
):
544 bl_idname
= "object.build_camera_rig"
545 bl_label
= "Build Camera Rig"
546 bl_description
= "Build a Camera Rig"
547 bl_options
= {'REGISTER', 'UNDO'}
549 mode
: bpy
.props
.EnumProperty(items
=(('DOLLY', 'Dolly', 'Dolly rig'),
550 ('CRANE', 'Crane', 'Crane rig',),
551 ('2D', '2D', '2D rig')),
553 description
="Type of camera to create",
556 def execute(self
, context
):
558 build_camera_rig(context
, self
.mode
)
562 def add_dolly_crane_buttons(self
, context
):
563 """Dolly and crane entries in the Add Object > Camera Menu"""
564 if context
.mode
== 'OBJECT':
565 self
.layout
.operator(
566 OBJECT_OT_build_camera_rig
.bl_idname
,
567 text
="Dolly Camera Rig",
571 self
.layout
.operator(
572 OBJECT_OT_build_camera_rig
.bl_idname
,
573 text
="Crane Camera Rig",
577 self
.layout
.operator(
578 OBJECT_OT_build_camera_rig
.bl_idname
,
579 text
="2D Camera Rig",
580 icon
='PIVOT_BOUNDBOX'
585 OBJECT_OT_build_camera_rig
,
590 from bpy
.utils
import register_class
594 bpy
.types
.VIEW3D_MT_camera_add
.append(add_dolly_crane_buttons
)
598 from bpy
.utils
import unregister_class
600 unregister_class(cls
)
602 bpy
.types
.VIEW3D_MT_camera_add
.remove(add_dolly_crane_buttons
)
605 if __name__
== "__main__":