1 # SPDX-FileCopyrightText: 2019-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 from bpy
.types
import Operator
7 from bpy_extras
import object_utils
8 from mathutils
import Vector
11 from .create_widgets
import (create_root_widget
,
12 create_camera_widget
, create_camera_offset_widget
,
13 create_aim_widget
, create_circle_widget
,
17 def create_prop_driver(rig
, cam
, prop_from
, prop_to
):
18 """Create driver to a property on the rig"""
19 driver
= cam
.data
.driver_add(prop_to
)
20 driver
.driver
.type = 'SCRIPTED'
21 var
= driver
.driver
.variables
.new()
23 var
.type = 'SINGLE_PROP'
25 # Target the custom bone property
26 var
.targets
[0].id = rig
27 var
.targets
[0].data_path
= 'pose.bones["Camera"]["%s"]' % prop_from
28 driver
.driver
.expression
= 'var'
31 def create_dolly_bones(rig
):
32 """Create bones for the dolly camera rig"""
33 bones
= rig
.data
.edit_bones
36 root
= bones
.new("Root")
37 root
.tail
= (0.0, 1.0, 0.0)
39 rig
.data
.collections
.new(name
="Controls")
40 rig
.data
.collections
['Controls'].assign(root
)
42 ctrl_aim_child
= bones
.new("MCH-Aim_shape_rotation")
43 ctrl_aim_child
.head
= (0.0, 10.0, 1.7)
44 ctrl_aim_child
.tail
= (0.0, 11.0, 1.7)
45 # Create bone collection and assign bone
46 rig
.data
.collections
.new(name
="MCH")
47 rig
.data
.collections
['MCH'].assign(ctrl_aim_child
)
48 rig
.data
.collections
['MCH'].is_visible
= False
50 ctrl_aim
= bones
.new("Aim")
51 ctrl_aim
.head
= (0.0, 10.0, 1.7)
52 ctrl_aim
.tail
= (0.0, 11.0, 1.7)
53 ctrl_aim
.show_wire
= True
54 rig
.data
.collections
['Controls'].assign(ctrl_aim
)
56 ctrl
= bones
.new("Camera")
57 ctrl
.head
= (0.0, 0.0, 1.7)
58 ctrl
.tail
= (0.0, 1.0, 1.7)
60 rig
.data
.collections
['Controls'].assign(ctrl
)
62 ctrl_offset
= bones
.new("Camera_Offset")
63 ctrl_offset
.head
= (0.0, 0.0, 1.7)
64 ctrl_offset
.tail
= (0.0, 1.0, 1.7)
65 ctrl_offset
.show_wire
= True
66 rig
.data
.collections
['Controls'].assign(ctrl_offset
)
71 ctrl_offset
.parent
= ctrl
72 ctrl_aim
.parent
= root
73 ctrl_aim_child
.parent
= ctrl_aim
75 # Jump into object mode
76 bpy
.ops
.object.mode_set(mode
='OBJECT')
77 pose_bones
= rig
.pose
.bones
78 # Lock the relevant scale channels of the Camera_offset bone
79 pose_bones
["Camera_Offset"].lock_scale
= (True,) * 3
82 def create_crane_bones(rig
):
83 """Create bones for the crane camera rig"""
84 bones
= rig
.data
.edit_bones
87 root
= bones
.new("Root")
88 root
.tail
= (0.0, 1.0, 0.0)
90 rig
.data
.collections
.new(name
="Controls")
91 rig
.data
.collections
['Controls'].assign(root
)
93 ctrl_aim_child
= bones
.new("MCH-Aim_shape_rotation")
94 ctrl_aim_child
.head
= (0.0, 10.0, 1.7)
95 ctrl_aim_child
.tail
= (0.0, 11.0, 1.7)
96 rig
.data
.collections
.new(name
="MCH")
97 rig
.data
.collections
['MCH'].assign(ctrl_aim_child
)
98 rig
.data
.collections
['MCH'].is_visible
= False
100 ctrl_aim
= bones
.new("Aim")
101 ctrl_aim
.head
= (0.0, 10.0, 1.7)
102 ctrl_aim
.tail
= (0.0, 11.0, 1.7)
103 ctrl_aim
.show_wire
= True
104 rig
.data
.collections
['Controls'].assign(ctrl_aim
)
106 ctrl
= bones
.new("Camera")
107 ctrl
.head
= (0.0, 1.0, 1.7)
108 ctrl
.tail
= (0.0, 2.0, 1.7)
109 rig
.data
.collections
['Controls'].assign(ctrl
)
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)
114 rig
.data
.collections
['Controls'].assign(ctrl_offset
)
116 arm
= bones
.new("Crane_Arm")
117 arm
.head
= (0.0, 0.0, 1.7)
118 arm
.tail
= (0.0, 1.0, 1.7)
119 rig
.data
.collections
['Controls'].assign(arm
)
121 height
= bones
.new("Crane_Height")
122 height
.head
= (0.0, 0.0, 0.0)
123 height
.tail
= (0.0, 0.0, 1.7)
124 rig
.data
.collections
['Controls'].assign(height
)
128 ctrl_offset
.parent
= ctrl
129 ctrl
.use_inherit_rotation
= False
130 ctrl
.inherit_scale
= "NONE"
131 ctrl
.show_wire
= True
134 arm
.inherit_scale
= "NONE"
137 ctrl_aim
.parent
= root
138 ctrl_aim_child
.parent
= ctrl_aim
140 # Jump into object mode
141 bpy
.ops
.object.mode_set(mode
='OBJECT')
142 pose_bones
= rig
.pose
.bones
144 # Lock the relevant loc, rot and scale
145 pose_bones
["Crane_Arm"].lock_rotation
= (False, True, False)
146 pose_bones
["Crane_Arm"].lock_scale
= (True, False, True)
147 pose_bones
["Crane_Height"].lock_location
= (True,) * 3
148 pose_bones
["Crane_Height"].lock_rotation
= (True,) * 3
149 pose_bones
["Crane_Height"].lock_scale
= (True, False, True)
150 pose_bones
["Camera_Offset"].lock_scale
= (True,) * 3
153 def setup_3d_rig(rig
, cam
):
154 """Finish setting up Dolly and Crane rigs"""
155 # Jump into object mode and change bones to euler
156 bpy
.ops
.object.mode_set(mode
='OBJECT')
157 pose_bones
= rig
.pose
.bones
158 for bone
in pose_bones
:
159 bone
.rotation_mode
= 'XYZ'
162 pb
= pose_bones
['Camera']
164 ui_data
= pb
.id_properties_ui("lens")
165 ui_data
.update(min=1.0, max=1000000.0, soft_max
= 5000.0, default
=50.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
["MCH-Aim_shape_rotation"]
182 # Add constraints to bones
183 con
= pose_bones
['MCH-Aim_shape_rotation'].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
210 rig
.data
.collections
.new(name
="Controls")
211 rig
.data
.collections
['Controls'].assign(root
)
213 ctrl
= bones
.new('Camera')
214 ctrl
.tail
= Vector((0.0, 0.0, 1.0))
215 ctrl
.show_wire
= True
216 rig
.data
.collections
['Controls'].assign(ctrl
)
218 left_corner
= bones
.new("Left_Corner")
219 left_corner
.head
= (-3, 10, -2)
220 left_corner
.tail
= left_corner
.head
+ Vector((0.0, 0.0, 1.0))
221 left_corner
.show_wire
= True
222 rig
.data
.collections
['Controls'].assign(left_corner
)
224 right_corner
= bones
.new("Right_Corner")
225 right_corner
.head
= (3, 10, -2)
226 right_corner
.tail
= right_corner
.head
+ Vector((0.0, 0.0, 1.0))
227 right_corner
.show_wire
= True
228 rig
.data
.collections
['Controls'].assign(right_corner
)
230 corner_distance_x
= (left_corner
.head
- right_corner
.head
).length
231 corner_distance_y
= -left_corner
.head
.z
232 corner_distance_z
= left_corner
.head
.y
233 rig
.data
.collections
['Controls'].assign(root
)
235 center
= bones
.new("MCH-Center")
236 center
.head
= ((right_corner
.head
+ left_corner
.head
) / 2.0)
237 center
.tail
= center
.head
+ Vector((0.0, 0.0, 1.0))
238 center
.show_wire
= True
239 rig
.data
.collections
.new(name
="MCH")
240 rig
.data
.collections
['MCH'].assign(center
)
241 rig
.data
.collections
['MCH'].is_visible
= False
242 center
.show_wire
= True
246 left_corner
.parent
= root
247 right_corner
.parent
= root
250 # Jump into object mode and change bones to euler
251 bpy
.ops
.object.mode_set(mode
='OBJECT')
252 pose_bones
= rig
.pose
.bones
253 for bone
in pose_bones
:
254 bone
.rotation_mode
= 'XYZ'
257 center_drivers
= pose_bones
["MCH-Center"].driver_add("location")
260 driver
= center_drivers
[0].driver
261 driver
.type = 'AVERAGE'
263 for corner
in ('left', 'right'):
264 var
= driver
.variables
.new()
266 var
.type = 'TRANSFORMS'
267 var
.targets
[0].id = rig
268 var
.targets
[0].bone_target
= corner
.capitalize() + '_Corner'
269 var
.targets
[0].transform_type
= 'LOC_X'
270 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
273 driver
= center_drivers
[1].driver
274 driver
.type = 'SCRIPTED'
276 driver
.expression
= '({distance_x} - (left_x-right_x))*(res_y/res_x)/2 + (left_y + right_y)/2'.format(
277 distance_x
=corner_distance_x
)
279 for direction
in ('x', 'y'):
280 for corner
in ('left', 'right'):
281 var
= driver
.variables
.new()
282 var
.name
= '%s_%s' % (corner
, direction
)
283 var
.type = 'TRANSFORMS'
284 var
.targets
[0].id = rig
285 var
.targets
[0].bone_target
= corner
.capitalize() + '_Corner'
286 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
287 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
289 var
= driver
.variables
.new()
290 var
.name
= 'res_' + direction
291 var
.type = 'SINGLE_PROP'
292 var
.targets
[0].id_type
= 'SCENE'
293 var
.targets
[0].id = scene
294 var
.targets
[0].data_path
= 'render.resolution_' + direction
297 driver
= center_drivers
[2].driver
298 driver
.type = 'AVERAGE'
300 for corner
in ('left', 'right'):
301 var
= driver
.variables
.new()
303 var
.type = 'TRANSFORMS'
304 var
.targets
[0].id = rig
305 var
.targets
[0].bone_target
= corner
.capitalize() + '_Corner'
306 var
.targets
[0].transform_type
= 'LOC_Z'
307 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
310 con
= pose_bones
["Camera"].constraints
.new('DAMPED_TRACK')
312 con
.subtarget
= "MCH-Center"
313 con
.track_axis
= 'TRACK_NEGATIVE_Z'
316 left_widget
= create_corner_widget("Left_Corner", reverse
=True)
317 right_widget
= create_corner_widget("Right_Corner")
318 parent_widget
= create_circle_widget("Root", radius
=0.5)
319 camera_widget
= create_circle_widget("Camera_2D", radius
=0.3)
321 # Add the custom bone shapes
322 pose_bones
["Left_Corner"].custom_shape
= left_widget
323 pose_bones
["Right_Corner"].custom_shape
= right_widget
324 pose_bones
["Root"].custom_shape
= parent_widget
325 pose_bones
["Camera"].custom_shape
= camera_widget
327 # Lock the relevant loc, rot and scale
328 pose_bones
["Left_Corner"].lock_rotation
= (True,) * 3
329 pose_bones
["Right_Corner"].lock_rotation
= (True,) * 3
330 pose_bones
["Camera"].lock_rotation
= (True,) * 3
331 pose_bones
["Camera"].lock_scale
= (True,) * 3
335 cam
.data
.sensor_fit
= "HORIZONTAL" # Avoids distortion in portrait format
337 # Property to switch between rotation and switch mode
338 pose_bones
["Camera"]['rotation_shift'] = 0.0
339 ui_data
= pose_bones
["Camera"].id_properties_ui('rotation_shift')
340 ui_data
.update(min=0.0, max=1.0, description
="rotation_shift")
342 # Rotation / shift switch driver
343 driver
= con
.driver_add('influence').driver
344 driver
.expression
= '1 - rotation_shift'
346 var
= driver
.variables
.new()
347 var
.name
= 'rotation_shift'
348 var
.type = 'SINGLE_PROP'
349 var
.targets
[0].id = rig
350 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
352 # Focal length driver
353 driver
= cam
.data
.driver_add('lens').driver
354 driver
.expression
= 'abs({distance_z} - (left_z + right_z)/2 + cam_z) * 36 / frame_width'.format(
355 distance_z
=corner_distance_z
)
357 var
= driver
.variables
.new()
358 var
.name
= 'frame_width'
359 var
.type = 'LOC_DIFF'
360 var
.targets
[0].id = rig
361 var
.targets
[0].bone_target
= "Left_Corner"
362 var
.targets
[0].transform_space
= 'WORLD_SPACE'
363 var
.targets
[1].id = rig
364 var
.targets
[1].bone_target
= "Right_Corner"
365 var
.targets
[1].transform_space
= 'WORLD_SPACE'
367 for corner
in ('left', 'right'):
368 var
= driver
.variables
.new()
369 var
.name
= corner
+ '_z'
370 var
.type = 'TRANSFORMS'
371 var
.targets
[0].id = rig
372 var
.targets
[0].bone_target
= corner
.capitalize() + '_Corner'
373 var
.targets
[0].transform_type
= 'LOC_Z'
374 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
376 var
= driver
.variables
.new()
378 var
.type = 'TRANSFORMS'
379 var
.targets
[0].id = rig
380 var
.targets
[0].bone_target
= "Camera"
381 var
.targets
[0].transform_type
= 'LOC_Z'
382 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
384 # Orthographic scale driver
385 driver
= cam
.data
.driver_add('ortho_scale').driver
386 driver
.expression
= 'abs({distance_x} - (left_x - right_x))'.format(distance_x
=corner_distance_x
)
388 for corner
in ('left', 'right'):
389 var
= driver
.variables
.new()
390 var
.name
= corner
+ '_x'
391 var
.type = 'TRANSFORMS'
392 var
.targets
[0].id = rig
393 var
.targets
[0].bone_target
= corner
.capitalize() + '_Corner'
394 var
.targets
[0].transform_type
= 'LOC_X'
395 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
398 driver
= cam
.data
.driver_add('shift_x').driver
400 driver
.expression
= 'rotation_shift * (((left_x + right_x)/2 - cam_x) * lens / abs({distance_z} - (left_z + right_z)/2 + cam_z) / 36)'.format(
401 distance_z
=corner_distance_z
)
403 var
= driver
.variables
.new()
404 var
.name
= 'rotation_shift'
405 var
.type = 'SINGLE_PROP'
406 var
.targets
[0].id = rig
407 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
409 for direction
in ('x', 'z'):
410 for corner
in ('left', 'right'):
411 var
= driver
.variables
.new()
412 var
.name
= '%s_%s' % (corner
, direction
)
413 var
.type = 'TRANSFORMS'
414 var
.targets
[0].id = rig
415 var
.targets
[0].bone_target
= corner
.capitalize() + '_Corner'
416 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
417 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
419 var
= driver
.variables
.new()
420 var
.name
= 'cam_' + direction
421 var
.type = 'TRANSFORMS'
422 var
.targets
[0].id = rig
423 var
.targets
[0].bone_target
= "Camera"
424 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
425 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
427 var
= driver
.variables
.new()
429 var
.type = 'SINGLE_PROP'
430 var
.targets
[0].id_type
= 'CAMERA'
431 var
.targets
[0].id = cam
.data
432 var
.targets
[0].data_path
= 'lens'
435 driver
= cam
.data
.driver_add('shift_y').driver
437 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(
438 distance_y
=corner_distance_y
, distance_z
=corner_distance_z
)
440 var
= driver
.variables
.new()
441 var
.name
= 'rotation_shift'
442 var
.type = 'SINGLE_PROP'
443 var
.targets
[0].id = rig
444 var
.targets
[0].data_path
= 'pose.bones["Camera"]["rotation_shift"]'
446 for direction
in ('y', 'z'):
447 for corner
in ('left', 'right'):
448 var
= driver
.variables
.new()
449 var
.name
= '%s_%s' % (corner
, direction
)
450 var
.type = 'TRANSFORMS'
451 var
.targets
[0].id = rig
452 var
.targets
[0].bone_target
= corner
.capitalize() + '_Corner'
453 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
454 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
456 var
= driver
.variables
.new()
457 var
.name
= 'cam_' + direction
458 var
.type = 'TRANSFORMS'
459 var
.targets
[0].id = rig
460 var
.targets
[0].bone_target
= "Camera"
461 var
.targets
[0].transform_type
= 'LOC_' + direction
.upper()
462 var
.targets
[0].transform_space
= 'TRANSFORM_SPACE'
464 for direction
in ('x', 'y'):
465 var
= driver
.variables
.new()
466 var
.name
= 'res_' + direction
467 var
.type = 'SINGLE_PROP'
468 var
.targets
[0].id_type
= 'SCENE'
469 var
.targets
[0].id = scene
470 var
.targets
[0].data_path
= 'render.resolution_' + direction
472 var
= driver
.variables
.new()
474 var
.type = 'SINGLE_PROP'
475 var
.targets
[0].id_type
= 'CAMERA'
476 var
.targets
[0].id = cam
.data
477 var
.targets
[0].data_path
= 'lens'
480 def build_camera_rig(context
, mode
):
481 """Create stuff common to all camera rigs."""
482 # Add the camera object
483 cam_name
= "%s_Camera" % mode
.capitalize()
484 cam_data
= bpy
.data
.cameras
.new(cam_name
)
485 cam
= object_utils
.object_data_add(context
, cam_data
, name
=cam_name
)
486 context
.scene
.camera
= cam
489 rig_name
= mode
.capitalize() + "_Rig"
490 rig_data
= bpy
.data
.armatures
.new(rig_name
)
491 rig
= object_utils
.object_data_add(context
, rig_data
, name
=rig_name
)
492 rig
["rig_id"] = "%s" % rig_name
493 rig
.location
= context
.scene
.cursor
.location
495 bpy
.ops
.object.mode_set(mode
='EDIT')
497 # Add new bones and setup specific rigs
499 create_dolly_bones(rig
)
500 setup_3d_rig(rig
, cam
)
501 elif mode
== "CRANE":
502 create_crane_bones(rig
)
503 setup_3d_rig(rig
, cam
)
505 create_2d_bones(context
, rig
, cam
)
507 # Parent the camera to the rig
508 cam
.location
= (0.0, -1.0, 0.0) # Move the camera to the correct position
510 cam
.parent_type
= "BONE"
512 cam
.parent_bone
= "Camera"
514 cam
.parent_bone
= "Camera_Offset"
516 # Change display to BBone: it just looks nicer
517 rig
.data
.display_type
= 'BBONE'
518 # Change display to wire for object
519 rig
.display_type
= 'WIRE'
521 # Lock camera transforms
522 cam
.lock_location
= (True,) * 3
523 cam
.lock_rotation
= (True,) * 3
524 cam
.lock_scale
= (True,) * 3
526 # Add custom properties to the armature’s Camera bone,
527 # so that all properties may be animated in a single action
529 pose_bones
= rig
.pose
.bones
531 # DOF Focus Distance property
532 pb
= pose_bones
['Camera']
533 pb
["focus_distance"] = 10.0
534 ui_data
= pb
.id_properties_ui('focus_distance')
535 ui_data
.update(min=0.0, default
=10.0)
537 # DOF F-Stop property
538 pb
= pose_bones
['Camera']
539 pb
["aperture_fstop"] = 2.8
540 ui_data
= pb
.id_properties_ui('aperture_fstop')
541 ui_data
.update(min=0.0, soft_min
=0.1, soft_max
=128.0, default
=2.8)
543 # Add drivers to link the camera properties to the custom props
545 create_prop_driver(rig
, cam
, "focus_distance", "dof.focus_distance")
546 create_prop_driver(rig
, cam
, "aperture_fstop", "dof.aperture_fstop")
548 # Make the rig the active object
549 view_layer
= context
.view_layer
550 for obj
in view_layer
.objects
:
551 obj
.select_set(False)
553 view_layer
.objects
.active
= rig
556 class OBJECT_OT_build_camera_rig(Operator
):
557 bl_idname
= "object.build_camera_rig"
558 bl_label
= "Build Camera Rig"
559 bl_description
= "Build a Camera Rig"
560 bl_options
= {'REGISTER', 'UNDO'}
562 mode
: bpy
.props
.EnumProperty(items
=(('DOLLY', 'Dolly', 'Dolly rig'),
563 ('CRANE', 'Crane', 'Crane rig',),
564 ('2D', '2D', '2D rig')),
566 description
="Type of camera to create",
569 def execute(self
, context
):
571 build_camera_rig(context
, self
.mode
)
575 def add_dolly_crane_buttons(self
, context
):
576 """Dolly and crane entries in the Add Object > Camera Menu"""
577 if context
.mode
== 'OBJECT':
578 self
.layout
.operator(
579 OBJECT_OT_build_camera_rig
.bl_idname
,
580 text
="Dolly Camera Rig",
584 self
.layout
.operator(
585 OBJECT_OT_build_camera_rig
.bl_idname
,
586 text
="Crane Camera Rig",
590 self
.layout
.operator(
591 OBJECT_OT_build_camera_rig
.bl_idname
,
592 text
="2D Camera Rig",
593 icon
='PIVOT_BOUNDBOX'
598 OBJECT_OT_build_camera_rig
,
603 from bpy
.utils
import register_class
607 bpy
.types
.VIEW3D_MT_camera_add
.append(add_dolly_crane_buttons
)
611 from bpy
.utils
import unregister_class
613 unregister_class(cls
)
615 bpy
.types
.VIEW3D_MT_camera_add
.remove(add_dolly_crane_buttons
)
618 if __name__
== "__main__":