1 # SPDX-License-Identifier: GPL-2.0-or-later
4 "name": "Turnaround Camera",
5 "author": "Antonio Vazquez (antonioya)",
8 "location": "View3D > Sidebar > View Tab > Turnaround Camera",
9 "description": "Add a camera rotation around selected object",
10 "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/turnaround_camera.html",
11 "category": "Animation",
17 from bpy
.props
import (
23 from bpy
.types
import (
30 # ------------------------------------------------------
32 # ------------------------------------------------------
33 class CAMERATURN_OT_RunAction(Operator
):
34 bl_idname
= "object.rotate_around"
35 bl_label
= "Turnaround"
36 bl_description
= "Create camera rotation around selected object"
37 bl_options
= {'REGISTER', 'UNDO'}
39 def execute(self
, context
):
40 # ----------------------
42 # ----------------------
44 turn_camera
= scene
.turn_camera
45 selectobject
= context
.active_object
47 savedcursor
= scene
.cursor
.location
.copy() # cursor position
48 savedframe
= scene
.frame_current
49 if turn_camera
.use_cursor
is False:
50 bpy
.ops
.view3d
.snap_cursor_to_selected()
52 # -------------------------
53 # Create empty and parent
54 # -------------------------
55 bpy
.ops
.object.empty_add(type='PLAIN_AXES')
56 myempty
= context
.active_object
58 myempty
.location
= selectobject
.location
59 savedstate
= myempty
.matrix_world
60 myempty
.parent
= selectobject
61 myempty
.name
= "MCH_Rotation_target"
62 myempty
.matrix_world
= savedstate
64 # -------------------------
65 # Parent camera to empty
66 # -------------------------
67 savedstate
= camera
.matrix_world
68 camera
.parent
= myempty
69 camera
.matrix_world
= savedstate
71 # -------------------------
73 # (make empty active object)
74 # -------------------------
75 bpy
.ops
.object.select_all(False)
76 myempty
.select_set(True)
77 context
.view_layer
.objects
.active
= myempty
78 # save current configuration
79 savedinterpolation
= context
.preferences
.edit
.keyframe_new_interpolation_type
80 # change interpolation mode
81 context
.preferences
.edit
.keyframe_new_interpolation_type
= 'LINEAR'
83 myempty
.rotation_euler
= (0, 0, 0)
84 myempty
.empty_display_size
= 0.1
85 scene
.frame_set(scene
.frame_start
)
86 myempty
.keyframe_insert(data_path
="rotation_euler", frame
=scene
.frame_start
)
88 # Clear the Camera Animations if the option is checked
89 if turn_camera
.reset_cam_anim
:
91 if bpy
.data
.cameras
[camera
.name
].animation_data
:
92 bpy
.data
.cameras
[camera
.name
].animation_data_clear()
93 except Exception as e
:
94 print("\n[Camera Turnaround]\nWarning: {}\n".format(e
))
97 if turn_camera
.dolly_zoom
!= "0":
98 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
99 bpy
.data
.cameras
[camera
.name
].keyframe_insert("lens", frame
=scene
.frame_start
)
101 # Calculate rotation XYZ
102 ix
= -1 if turn_camera
.inverse_x
else 1
103 iy
= -1 if turn_camera
.inverse_y
else 1
104 iz
= -1 if turn_camera
.inverse_z
else 1
106 xrot
= (pi
* 2) * turn_camera
.camera_revol_x
* ix
107 yrot
= (pi
* 2) * turn_camera
.camera_revol_y
* iy
108 zrot
= (pi
* 2) * turn_camera
.camera_revol_z
* iz
110 # create middle frame
111 if turn_camera
.back_forw
is True:
112 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
113 myempty
.keyframe_insert(
114 data_path
="rotation_euler",
115 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
123 if turn_camera
.dolly_zoom
== "2":
124 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
125 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
127 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
131 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
132 myempty
.keyframe_insert(data_path
="rotation_euler", frame
=scene
.frame_end
)
134 if turn_camera
.dolly_zoom
!= "0":
135 if turn_camera
.dolly_zoom
== "1":
136 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
# final
138 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
# back to init
140 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
141 "lens", frame
=scene
.frame_end
145 if turn_camera
.track
is True:
146 bpy
.context
.view_layer
.objects
.active
= camera
147 bpy
.ops
.object.constraint_add(type='TRACK_TO')
148 bpy
.context
.object.constraints
[-1].track_axis
= 'TRACK_NEGATIVE_Z'
149 bpy
.context
.object.constraints
[-1].up_axis
= 'UP_Y'
150 bpy
.context
.object.constraints
[-1].target
= myempty
152 # back previous configuration
153 context
.preferences
.edit
.keyframe_new_interpolation_type
= savedinterpolation
154 scene
.cursor
.location
= savedcursor
156 # -------------------------
157 # Back to old selection
158 # -------------------------
159 bpy
.ops
.object.select_all(False)
160 selectobject
.select_set(True)
161 bpy
.context
.view_layer
.objects
.active
= selectobject
162 scene
.frame_set(savedframe
)
167 # ------------------------------------------------------
169 # ------------------------------------------------------
170 class CAMERATURN_Props(PropertyGroup
):
172 camera_revol_x
: FloatProperty(
173 name
="X", min=0, max=25,
174 default
=0, precision
=2,
175 description
="Number total of revolutions in X axis"
177 camera_revol_y
: FloatProperty(
178 name
="Y", min=0, max=25,
179 default
=0, precision
=2,
180 description
="Number total of revolutions in Y axis"
182 camera_revol_z
: FloatProperty(
183 name
="Z", min=0, max=25,
184 default
=1, precision
=2,
185 description
="Number total of revolutions in Z axis"
187 inverse_x
: BoolProperty(
189 description
="Inverse rotation",
192 inverse_y
: BoolProperty(
194 description
="Inverse rotation",
197 inverse_z
: BoolProperty(
199 description
="Inverse rotation",
202 use_cursor
: BoolProperty(
203 name
="Use cursor position",
204 description
="Use cursor position instead of object origin",
207 back_forw
: BoolProperty(
208 name
="Back and forward",
209 description
="Create back and forward animation",
212 dolly_zoom
: EnumProperty(
215 ('1', "Dolly zoom", ""),
216 ('2', "Dolly zoom B/F", "")
219 description
="Create a camera lens movement"
221 camera_from_lens
: FloatProperty(
223 min=1, max=500, default
=35,
225 description
="Start lens value"
227 camera_to_lens
: FloatProperty(
230 default
=35, precision
=3,
231 description
="End lens value"
234 name
="Create track constraint",
235 description
="Add a track constraint to the camera",
238 reset_cam_anim
: BoolProperty(
240 description
="Clear previous camera animations if there are any\n"
241 "(For instance, previous Dolly Zoom)",
246 # ------------------------------------------------------
248 # ------------------------------------------------------
249 class CAMERATURN_PT_ui(Panel
):
250 bl_idname
= "CAMERA_TURN_PT_main"
251 bl_label
= "Turnaround Camera"
252 bl_space_type
= "VIEW_3D"
253 bl_region_type
= "UI"
254 bl_category
= "Animate"
255 bl_context
= "objectmode"
256 bl_options
= {'DEFAULT_CLOSED'}
258 def draw(self
, context
):
260 scene
= context
.scene
261 turn_camera
= scene
.turn_camera
265 except AttributeError:
266 row
= layout
.row(align
=False)
267 row
.label(text
="No defined camera for scene", icon
="INFO")
270 if context
.active_object
is not None:
271 if context
.active_object
.type != 'CAMERA':
272 buf
= context
.active_object
.name
273 row
= layout
.row(align
=True)
274 row
.operator("object.rotate_around", icon
='OUTLINER_DATA_CAMERA')
277 box
.label(text
=buf
, icon
='MESH_DATA')
278 row
= layout
.row(align
=False)
279 row
.prop(scene
, "camera")
281 layout
.label(text
="Rotation:")
282 row
= layout
.row(align
=True)
283 row
.prop(scene
, "frame_start")
284 row
.prop(scene
, "frame_end")
286 col
= layout
.column(align
=True)
287 split
= col
.split(factor
=0.85, align
=True)
288 split
.prop(turn_camera
, "camera_revol_x")
289 split
.prop(turn_camera
, "inverse_x", toggle
=True)
290 split
= col
.split(factor
=0.85, align
=True)
291 split
.prop(turn_camera
, "camera_revol_y")
292 split
.prop(turn_camera
, "inverse_y", toggle
=True)
293 split
= col
.split(factor
=0.85, align
=True)
294 split
.prop(turn_camera
, "camera_revol_z")
295 split
.prop(turn_camera
, "inverse_z", toggle
=True)
297 col
= layout
.column(align
=True)
298 col
.label(text
="Options:")
299 row
= col
.row(align
=True)
300 row
.prop(turn_camera
, "back_forw", toggle
=True)
301 row
.prop(turn_camera
, "reset_cam_anim", toggle
=True)
302 col
.prop(turn_camera
, "track", toggle
=True)
303 col
.prop(turn_camera
, "use_cursor", toggle
=True)
306 row
.prop(turn_camera
, "dolly_zoom")
307 if turn_camera
.dolly_zoom
!= "0":
308 row
= layout
.row(align
=True)
309 row
.prop(turn_camera
, "camera_from_lens")
310 row
.prop(turn_camera
, "camera_to_lens")
313 buf
= "No valid object selected"
314 layout
.label(text
=buf
, icon
='MESH_DATA')
317 # ------------------------------------------------------
319 # ------------------------------------------------------
321 CAMERATURN_OT_RunAction
,
328 from bpy
.utils
import register_class
332 bpy
.types
.Scene
.turn_camera
= PointerProperty(type=CAMERATURN_Props
)
336 from bpy
.utils
import unregister_class
337 for cls
in reversed(classes
):
338 unregister_class(cls
)
340 del bpy
.types
.Scene
.turn_camera
343 if __name__
== "__main__":