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 "name": "Turnaround Camera",
21 "author": "Antonio Vazquez (antonioya)",
23 "blender": (2, 80, 0),
24 "location": "View3D > Sidebar > View Tab > Turnaround Camera",
25 "description": "Add a camera rotation around selected object",
26 "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/turnaround_camera.html",
27 "category": "Animation",
33 from bpy
.props
import (
39 from bpy
.types
import (
46 # ------------------------------------------------------
48 # ------------------------------------------------------
49 class CAMERATURN_OT_RunAction(Operator
):
50 bl_idname
= "object.rotate_around"
51 bl_label
= "Turnaround"
52 bl_description
= "Create camera rotation around selected object"
53 bl_options
= {'REGISTER', 'UNDO'}
55 def execute(self
, context
):
56 # ----------------------
58 # ----------------------
60 turn_camera
= scene
.turn_camera
61 selectobject
= context
.active_object
63 savedcursor
= scene
.cursor
.location
.copy() # cursor position
64 savedframe
= scene
.frame_current
65 if turn_camera
.use_cursor
is False:
66 bpy
.ops
.view3d
.snap_cursor_to_selected()
68 # -------------------------
69 # Create empty and parent
70 # -------------------------
71 bpy
.ops
.object.empty_add(type='PLAIN_AXES')
72 myempty
= context
.active_object
74 myempty
.location
= selectobject
.location
75 savedstate
= myempty
.matrix_world
76 myempty
.parent
= selectobject
77 myempty
.name
= "MCH_Rotation_target"
78 myempty
.matrix_world
= savedstate
80 # -------------------------
81 # Parent camera to empty
82 # -------------------------
83 savedstate
= camera
.matrix_world
84 camera
.parent
= myempty
85 camera
.matrix_world
= savedstate
87 # -------------------------
89 # (make empty active object)
90 # -------------------------
91 bpy
.ops
.object.select_all(False)
92 myempty
.select_set(True)
93 context
.view_layer
.objects
.active
= myempty
94 # save current configuration
95 savedinterpolation
= context
.preferences
.edit
.keyframe_new_interpolation_type
96 # change interpolation mode
97 context
.preferences
.edit
.keyframe_new_interpolation_type
= 'LINEAR'
99 myempty
.rotation_euler
= (0, 0, 0)
100 myempty
.empty_display_size
= 0.1
101 scene
.frame_set(scene
.frame_start
)
102 myempty
.keyframe_insert(data_path
="rotation_euler", frame
=scene
.frame_start
)
104 # Clear the Camera Animations if the option is checked
105 if turn_camera
.reset_cam_anim
:
107 if bpy
.data
.cameras
[camera
.name
].animation_data
:
108 bpy
.data
.cameras
[camera
.name
].animation_data_clear()
109 except Exception as e
:
110 print("\n[Camera Turnaround]\nWarning: {}\n".format(e
))
113 if turn_camera
.dolly_zoom
!= "0":
114 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
115 bpy
.data
.cameras
[camera
.name
].keyframe_insert("lens", frame
=scene
.frame_start
)
117 # Calculate rotation XYZ
118 ix
= -1 if turn_camera
.inverse_x
else 1
119 iy
= -1 if turn_camera
.inverse_y
else 1
120 iz
= -1 if turn_camera
.inverse_z
else 1
122 xrot
= (pi
* 2) * turn_camera
.camera_revol_x
* ix
123 yrot
= (pi
* 2) * turn_camera
.camera_revol_y
* iy
124 zrot
= (pi
* 2) * turn_camera
.camera_revol_z
* iz
126 # create middle frame
127 if turn_camera
.back_forw
is True:
128 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
129 myempty
.keyframe_insert(
130 data_path
="rotation_euler",
131 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
139 if turn_camera
.dolly_zoom
== "2":
140 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
141 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
143 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
147 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
148 myempty
.keyframe_insert(data_path
="rotation_euler", frame
=scene
.frame_end
)
150 if turn_camera
.dolly_zoom
!= "0":
151 if turn_camera
.dolly_zoom
== "1":
152 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
# final
154 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
# back to init
156 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
157 "lens", frame
=scene
.frame_end
161 if turn_camera
.track
is True:
162 bpy
.context
.view_layer
.objects
.active
= camera
163 bpy
.ops
.object.constraint_add(type='TRACK_TO')
164 bpy
.context
.object.constraints
[-1].track_axis
= 'TRACK_NEGATIVE_Z'
165 bpy
.context
.object.constraints
[-1].up_axis
= 'UP_Y'
166 bpy
.context
.object.constraints
[-1].target
= bpy
.data
.objects
[myempty
.name
]
168 # back previous configuration
169 context
.preferences
.edit
.keyframe_new_interpolation_type
= savedinterpolation
170 scene
.cursor
.location
= savedcursor
172 # -------------------------
173 # Back to old selection
174 # -------------------------
175 bpy
.ops
.object.select_all(False)
176 selectobject
.select_set(True)
177 bpy
.context
.view_layer
.objects
.active
= selectobject
178 scene
.frame_set(savedframe
)
183 # ------------------------------------------------------
185 # ------------------------------------------------------
186 class CAMERATURN_Props(PropertyGroup
):
188 camera_revol_x
: FloatProperty(
189 name
="X", min=0, max=25,
190 default
=0, precision
=2,
191 description
="Number total of revolutions in X axis"
193 camera_revol_y
: FloatProperty(
194 name
="Y", min=0, max=25,
195 default
=0, precision
=2,
196 description
="Number total of revolutions in Y axis"
198 camera_revol_z
: FloatProperty(
199 name
="Z", min=0, max=25,
200 default
=1, precision
=2,
201 description
="Number total of revolutions in Z axis"
203 inverse_x
: BoolProperty(
205 description
="Inverse rotation",
208 inverse_y
: BoolProperty(
210 description
="Inverse rotation",
213 inverse_z
: BoolProperty(
215 description
="Inverse rotation",
218 use_cursor
: BoolProperty(
219 name
="Use cursor position",
220 description
="Use cursor position instead of object origin",
223 back_forw
: BoolProperty(
224 name
="Back and forward",
225 description
="Create back and forward animation",
228 dolly_zoom
: EnumProperty(
231 ('1', "Dolly zoom", ""),
232 ('2', "Dolly zoom B/F", "")
235 description
="Create a camera lens movement"
237 camera_from_lens
: FloatProperty(
239 min=1, max=500, default
=35,
241 description
="Start lens value"
243 camera_to_lens
: FloatProperty(
246 default
=35, precision
=3,
247 description
="End lens value"
250 name
="Create track constraint",
251 description
="Add a track constraint to the camera",
254 reset_cam_anim
: BoolProperty(
256 description
="Clear previous camera animations if there are any\n"
257 "(For instance, previous Dolly Zoom)",
262 # ------------------------------------------------------
264 # ------------------------------------------------------
265 class CAMERATURN_PT_ui(Panel
):
266 bl_idname
= "CAMERA_TURN_PT_main"
267 bl_label
= "Turnaround Camera"
268 bl_space_type
= "VIEW_3D"
269 bl_region_type
= "UI"
270 bl_category
= "Animate"
271 bl_context
= "objectmode"
272 bl_options
= {'DEFAULT_CLOSED'}
274 def draw(self
, context
):
276 scene
= context
.scene
277 turn_camera
= scene
.turn_camera
281 except AttributeError:
282 row
= layout
.row(align
=False)
283 row
.label(text
="No defined camera for scene", icon
="INFO")
286 if context
.active_object
is not None:
287 if context
.active_object
.type != 'CAMERA':
288 buf
= context
.active_object
.name
289 row
= layout
.row(align
=True)
290 row
.operator("object.rotate_around", icon
='OUTLINER_DATA_CAMERA')
293 box
.label(text
=buf
, icon
='MESH_DATA')
294 row
= layout
.row(align
=False)
295 row
.prop(scene
, "camera")
297 layout
.label(text
="Rotation:")
298 row
= layout
.row(align
=True)
299 row
.prop(scene
, "frame_start")
300 row
.prop(scene
, "frame_end")
302 col
= layout
.column(align
=True)
303 split
= col
.split(factor
=0.85, align
=True)
304 split
.prop(turn_camera
, "camera_revol_x")
305 split
.prop(turn_camera
, "inverse_x", toggle
=True)
306 split
= col
.split(factor
=0.85, align
=True)
307 split
.prop(turn_camera
, "camera_revol_y")
308 split
.prop(turn_camera
, "inverse_y", toggle
=True)
309 split
= col
.split(factor
=0.85, align
=True)
310 split
.prop(turn_camera
, "camera_revol_z")
311 split
.prop(turn_camera
, "inverse_z", toggle
=True)
313 col
= layout
.column(align
=True)
314 col
.label(text
="Options:")
315 row
= col
.row(align
=True)
316 row
.prop(turn_camera
, "back_forw", toggle
=True)
317 row
.prop(turn_camera
, "reset_cam_anim", toggle
=True)
318 col
.prop(turn_camera
, "track", toggle
=True)
319 col
.prop(turn_camera
, "use_cursor", toggle
=True)
322 row
.prop(turn_camera
, "dolly_zoom")
323 if turn_camera
.dolly_zoom
!= "0":
324 row
= layout
.row(align
=True)
325 row
.prop(turn_camera
, "camera_from_lens")
326 row
.prop(turn_camera
, "camera_to_lens")
329 buf
= "No valid object selected"
330 layout
.label(text
=buf
, icon
='MESH_DATA')
333 # ------------------------------------------------------
335 # ------------------------------------------------------
337 CAMERATURN_OT_RunAction
,
344 from bpy
.utils
import register_class
348 bpy
.types
.Scene
.turn_camera
= PointerProperty(type=CAMERATURN_Props
)
352 from bpy
.utils
import unregister_class
353 for cls
in reversed(classes
):
354 unregister_class(cls
)
356 del bpy
.types
.Scene
.turn_camera
359 if __name__
== "__main__":