OBJ: reintroduce export menu item for the old (pre-3.1) python exporter
[blender-addons.git] / add_camera_rigs / build_rigs.py
blob9acce660fb876d4cf6e1fd08a1d8f3a25b9558b1
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 #####
19 import bpy
20 from bpy.types import Operator
21 from bpy_extras import object_utils
22 from mathutils import Vector
23 from math import pi
25 from .create_widgets import (create_root_widget,
26 create_camera_widget, create_camera_offset_widget,
27 create_aim_widget, create_circle_widget,
28 create_corner_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()
36 var.name = 'var'
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
49 # Add new bones
50 root = bones.new("Root")
51 root.tail = (0.0, 1.0, 0.0)
52 root.show_wire = True
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)
67 ctrl.show_wire = True
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
74 # Setup hierarchy
75 ctrl.parent = root
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
91 # Add new bones
92 root = bones.new("Root")
93 root.tail = (0.0, 1.0, 0.0)
94 root.show_wire = True
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)
122 # Setup hierarchy
123 ctrl.parent = arm
124 ctrl_offset.parent = ctrl
125 ctrl.use_inherit_rotation = False
126 ctrl.use_inherit_scale = False
127 ctrl.show_wire = True
129 arm.parent = height
130 arm.use_inherit_scale = False
132 height.parent = root
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'
157 # Lens property
158 pb = pose_bones['Camera']
159 pb["lens"] = 50.0
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)
163 # Build the widgets
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')
180 con.target = rig
181 con.subtarget = "Camera"
183 con = pose_bones['Camera'].constraints.new('TRACK_TO')
184 con.track_axis = 'TRACK_Y'
185 con.up_axis = 'UP_Z'
186 con.target = rig
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
201 # Add new 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
231 # Setup hierarchy
232 ctrl.parent = root
233 left_corner.parent = root
234 right_corner.parent = root
235 center.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'
243 # Bone drivers
244 center_drivers = pose_bones["Center-MCH"].driver_add("location")
246 # Center X driver
247 driver = center_drivers[0].driver
248 driver.type = 'AVERAGE'
250 for corner in ('left', 'right'):
251 var = driver.variables.new()
252 var.name = corner
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'
259 # Center Y driver
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
283 # Center Z driver
284 driver = center_drivers[2].driver
285 driver.type = 'AVERAGE'
287 for corner in ('left', 'right'):
288 var = driver.variables.new()
289 var.name = corner
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'
296 # Bone constraints
297 con = pose_bones["Camera"].constraints.new('DAMPED_TRACK')
298 con.target = rig
299 con.subtarget = "Center-MCH"
300 con.track_axis = 'TRACK_NEGATIVE_Z'
302 # Build the widgets
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
320 # Camera settings
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()
364 var.name = 'cam_z'
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'
384 # Shift driver X
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()
415 var.name = 'lens'
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'
421 # Shift driver Y
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()
460 var.name = 'lens'
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
475 # Add the rig object
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
485 if mode == "DOLLY":
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)
491 elif mode == "2D":
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
496 cam.parent = rig
497 cam.parent_type = "BONE"
498 if mode == "2D":
499 cam.parent_bone = "Camera"
500 else:
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
531 # on the armature
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)
539 rig.select_set(True)
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')),
552 name="mode",
553 description="Type of camera to create",
554 default="DOLLY")
556 def execute(self, context):
557 # Build the rig
558 build_camera_rig(context, self.mode)
559 return {'FINISHED'}
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",
568 icon='VIEW_CAMERA'
569 ).mode = "DOLLY"
571 self.layout.operator(
572 OBJECT_OT_build_camera_rig.bl_idname,
573 text="Crane Camera Rig",
574 icon='VIEW_CAMERA'
575 ).mode = "CRANE"
577 self.layout.operator(
578 OBJECT_OT_build_camera_rig.bl_idname,
579 text="2D Camera Rig",
580 icon='PIVOT_BOUNDBOX'
581 ).mode = "2D"
584 classes = (
585 OBJECT_OT_build_camera_rig,
589 def register():
590 from bpy.utils import register_class
591 for cls in classes:
592 register_class(cls)
594 bpy.types.VIEW3D_MT_camera_add.append(add_dolly_crane_buttons)
597 def unregister():
598 from bpy.utils import unregister_class
599 for cls in classes:
600 unregister_class(cls)
602 bpy.types.VIEW3D_MT_camera_add.remove(add_dolly_crane_buttons)
605 if __name__ == "__main__":
606 register()