1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "Turnaround Camera",
7 "author": "Antonio Vazquez (antonioya)",
10 "location": "View3D > Sidebar > View Tab > Turnaround Camera",
11 "description": "Add a camera rotation around selected object",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/turnaround_camera.html",
13 "category": "Animation",
19 from bpy
.props
import (
25 from bpy
.types
import (
32 # ------------------------------------------------------
34 # ------------------------------------------------------
35 class CAMERATURN_OT_RunAction(Operator
):
36 bl_idname
= "object.rotate_around"
37 bl_label
= "Turnaround"
38 bl_description
= "Create camera rotation around selected object"
39 bl_options
= {'REGISTER', 'UNDO'}
41 def execute(self
, context
):
42 # ----------------------
44 # ----------------------
46 turn_camera
= scene
.turn_camera
47 selectobject
= context
.active_object
49 savedcursor
= scene
.cursor
.location
.copy() # cursor position
50 savedframe
= scene
.frame_current
51 if turn_camera
.use_cursor
is False:
52 bpy
.ops
.view3d
.snap_cursor_to_selected()
54 # -------------------------
55 # Create empty and parent
56 # -------------------------
57 bpy
.ops
.object.empty_add(type='PLAIN_AXES')
58 myempty
= context
.active_object
60 myempty
.location
= selectobject
.location
61 savedstate
= myempty
.matrix_world
62 myempty
.parent
= selectobject
63 myempty
.name
= "MCH_Rotation_target"
64 myempty
.matrix_world
= savedstate
66 # -------------------------
67 # Parent camera to empty
68 # -------------------------
69 savedstate
= camera
.matrix_world
70 camera
.parent
= myempty
71 camera
.matrix_world
= savedstate
73 # -------------------------
75 # (make empty active object)
76 # -------------------------
77 bpy
.ops
.object.select_all(False)
78 myempty
.select_set(True)
79 context
.view_layer
.objects
.active
= myempty
80 # save current configuration
81 savedinterpolation
= context
.preferences
.edit
.keyframe_new_interpolation_type
82 # change interpolation mode
83 context
.preferences
.edit
.keyframe_new_interpolation_type
= 'LINEAR'
85 myempty
.rotation_euler
= (0, 0, 0)
86 myempty
.empty_display_size
= 0.1
87 scene
.frame_set(scene
.frame_start
)
88 myempty
.keyframe_insert(data_path
="rotation_euler", frame
=scene
.frame_start
)
90 # Clear the Camera Animations if the option is checked
91 if turn_camera
.reset_cam_anim
:
93 if bpy
.data
.cameras
[camera
.name
].animation_data
:
94 bpy
.data
.cameras
[camera
.name
].animation_data_clear()
95 except Exception as e
:
96 print("\n[Camera Turnaround]\nWarning: {}\n".format(e
))
99 if turn_camera
.dolly_zoom
!= "0":
100 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
101 bpy
.data
.cameras
[camera
.name
].keyframe_insert("lens", frame
=scene
.frame_start
)
103 # Calculate rotation XYZ
104 ix
= -1 if turn_camera
.inverse_x
else 1
105 iy
= -1 if turn_camera
.inverse_y
else 1
106 iz
= -1 if turn_camera
.inverse_z
else 1
108 xrot
= (pi
* 2) * turn_camera
.camera_revol_x
* ix
109 yrot
= (pi
* 2) * turn_camera
.camera_revol_y
* iy
110 zrot
= (pi
* 2) * turn_camera
.camera_revol_z
* iz
112 # create middle frame
113 if turn_camera
.back_forw
is True:
114 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
115 myempty
.keyframe_insert(
116 data_path
="rotation_euler",
117 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
125 if turn_camera
.dolly_zoom
== "2":
126 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
127 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
129 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
133 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
134 myempty
.keyframe_insert(data_path
="rotation_euler", frame
=scene
.frame_end
)
136 if turn_camera
.dolly_zoom
!= "0":
137 if turn_camera
.dolly_zoom
== "1":
138 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
# final
140 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
# back to init
142 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
143 "lens", frame
=scene
.frame_end
147 if turn_camera
.track
is True:
148 bpy
.context
.view_layer
.objects
.active
= camera
149 bpy
.ops
.object.constraint_add(type='TRACK_TO')
150 bpy
.context
.object.constraints
[-1].track_axis
= 'TRACK_NEGATIVE_Z'
151 bpy
.context
.object.constraints
[-1].up_axis
= 'UP_Y'
152 bpy
.context
.object.constraints
[-1].target
= myempty
154 # back previous configuration
155 context
.preferences
.edit
.keyframe_new_interpolation_type
= savedinterpolation
156 scene
.cursor
.location
= savedcursor
158 # -------------------------
159 # Back to old selection
160 # -------------------------
161 bpy
.ops
.object.select_all(False)
162 selectobject
.select_set(True)
163 bpy
.context
.view_layer
.objects
.active
= selectobject
164 scene
.frame_set(savedframe
)
169 # ------------------------------------------------------
171 # ------------------------------------------------------
172 class CAMERATURN_Props(PropertyGroup
):
174 camera_revol_x
: FloatProperty(
175 name
="X", min=0, max=25,
176 default
=0, precision
=2,
177 description
="Number total of revolutions in X axis"
179 camera_revol_y
: FloatProperty(
180 name
="Y", min=0, max=25,
181 default
=0, precision
=2,
182 description
="Number total of revolutions in Y axis"
184 camera_revol_z
: FloatProperty(
185 name
="Z", min=0, max=25,
186 default
=1, precision
=2,
187 description
="Number total of revolutions in Z axis"
189 inverse_x
: BoolProperty(
191 description
="Inverse rotation",
194 inverse_y
: BoolProperty(
196 description
="Inverse rotation",
199 inverse_z
: BoolProperty(
201 description
="Inverse rotation",
204 use_cursor
: BoolProperty(
205 name
="Use cursor position",
206 description
="Use cursor position instead of object origin",
209 back_forw
: BoolProperty(
210 name
="Back and forward",
211 description
="Create back and forward animation",
214 dolly_zoom
: EnumProperty(
217 ('1', "Dolly zoom", ""),
218 ('2', "Dolly zoom B/F", "")
221 description
="Create a camera lens movement"
223 camera_from_lens
: FloatProperty(
225 min=1, max=500, default
=35,
227 description
="Start lens value"
229 camera_to_lens
: FloatProperty(
232 default
=35, precision
=3,
233 description
="End lens value"
236 name
="Create track constraint",
237 description
="Add a track constraint to the camera",
240 reset_cam_anim
: BoolProperty(
242 description
="Clear previous camera animations if there are any\n"
243 "(For instance, previous Dolly Zoom)",
248 # ------------------------------------------------------
250 # ------------------------------------------------------
251 class CAMERATURN_PT_ui(Panel
):
252 bl_idname
= "CAMERA_TURN_PT_main"
253 bl_label
= "Turnaround Camera"
254 bl_space_type
= "VIEW_3D"
255 bl_region_type
= "UI"
256 bl_category
= "Animate"
257 bl_context
= "objectmode"
258 bl_options
= {'DEFAULT_CLOSED'}
260 def draw(self
, context
):
262 scene
= context
.scene
263 turn_camera
= scene
.turn_camera
267 except AttributeError:
268 row
= layout
.row(align
=False)
269 row
.label(text
="No defined camera for scene", icon
="INFO")
272 if context
.active_object
is not None:
273 if context
.active_object
.type != 'CAMERA':
274 buf
= context
.active_object
.name
275 row
= layout
.row(align
=True)
276 row
.operator("object.rotate_around", icon
='OUTLINER_DATA_CAMERA')
279 box
.label(text
=buf
, icon
='MESH_DATA')
280 row
= layout
.row(align
=False)
281 row
.prop(scene
, "camera")
283 layout
.label(text
="Rotation:")
284 row
= layout
.row(align
=True)
285 row
.prop(scene
, "frame_start")
286 row
.prop(scene
, "frame_end")
288 col
= layout
.column(align
=True)
289 split
= col
.split(factor
=0.85, align
=True)
290 split
.prop(turn_camera
, "camera_revol_x")
291 split
.prop(turn_camera
, "inverse_x", toggle
=True)
292 split
= col
.split(factor
=0.85, align
=True)
293 split
.prop(turn_camera
, "camera_revol_y")
294 split
.prop(turn_camera
, "inverse_y", toggle
=True)
295 split
= col
.split(factor
=0.85, align
=True)
296 split
.prop(turn_camera
, "camera_revol_z")
297 split
.prop(turn_camera
, "inverse_z", toggle
=True)
299 col
= layout
.column(align
=True)
300 col
.label(text
="Options:")
301 row
= col
.row(align
=True)
302 row
.prop(turn_camera
, "back_forw", toggle
=True)
303 row
.prop(turn_camera
, "reset_cam_anim", toggle
=True)
304 col
.prop(turn_camera
, "track", toggle
=True)
305 col
.prop(turn_camera
, "use_cursor", toggle
=True)
308 row
.prop(turn_camera
, "dolly_zoom")
309 if turn_camera
.dolly_zoom
!= "0":
310 row
= layout
.row(align
=True)
311 row
.prop(turn_camera
, "camera_from_lens")
312 row
.prop(turn_camera
, "camera_to_lens")
315 buf
= "No valid object selected"
316 layout
.label(text
=buf
, icon
='MESH_DATA')
319 # ------------------------------------------------------
321 # ------------------------------------------------------
323 CAMERATURN_OT_RunAction
,
330 from bpy
.utils
import register_class
334 bpy
.types
.Scene
.turn_camera
= PointerProperty(type=CAMERATURN_Props
)
338 from bpy
.utils
import unregister_class
339 for cls
in reversed(classes
):
340 unregister_class(cls
)
342 del bpy
.types
.Scene
.turn_camera
345 if __name__
== "__main__":