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 "wiki_url": "https://docs.blender.org/manual/en/dev/addons/"
27 "animation/turnaround_camera.html",
28 "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"
54 def execute(self
, context
):
55 # ----------------------
57 # ----------------------
59 turn_camera
= scene
.turn_camera
60 selectobject
= context
.active_object
61 camera
= context
.scene
.camera
62 savedcursor
= bpy
.context
.scene
.cursor
.location
.copy() # cursor position
63 savedframe
= scene
.frame_current
64 if turn_camera
.use_cursor
is False:
65 bpy
.ops
.view3d
.snap_cursor_to_selected()
67 # -------------------------
68 # Create empty and parent
69 # -------------------------
70 bpy
.ops
.object.empty_add(type='PLAIN_AXES')
71 myempty
= context
.active_object
73 myempty
.location
= selectobject
.location
74 savedstate
= myempty
.matrix_world
75 myempty
.parent
= selectobject
76 myempty
.name
= 'MCH_Rotation_target'
77 myempty
.matrix_world
= savedstate
79 # -------------------------
80 # Parent camera to empty
81 # -------------------------
82 savedstate
= camera
.matrix_world
83 camera
.parent
= myempty
84 camera
.matrix_world
= savedstate
86 # -------------------------
88 # (make empty active object)
89 # -------------------------
90 bpy
.ops
.object.select_all(False)
91 myempty
.select_set(True)
92 context
.view_layer
.objects
.active
= myempty
93 # save current configuration
94 savedinterpolation
= context
.preferences
.edit
.keyframe_new_interpolation_type
95 # change interpolation mode
96 context
.preferences
.edit
.keyframe_new_interpolation_type
= 'LINEAR'
98 myempty
.rotation_euler
= (0, 0, 0)
99 myempty
.empty_display_size
= 0.1
100 context
.scene
.frame_set(scene
.frame_start
)
101 myempty
.keyframe_insert(data_path
='rotation_euler', frame
=scene
.frame_start
)
103 # Clear the Camera Animations if the option is checked
104 if turn_camera
.reset_cam_anim
:
106 if bpy
.data
.cameras
[camera
.name
].animation_data
:
107 bpy
.data
.cameras
[camera
.name
].animation_data_clear()
108 except Exception as e
:
109 print("\n[Camera Turnaround]\nWarning: {}\n".format(e
))
112 if turn_camera
.dolly_zoom
!= "0":
113 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
114 bpy
.data
.cameras
[camera
.name
].keyframe_insert('lens', frame
=scene
.frame_start
)
116 # Calculate rotation XYZ
117 ix
= -1 if turn_camera
.inverse_x
else 1
118 iy
= -1 if turn_camera
.inverse_y
else 1
119 iz
= -1 if turn_camera
.inverse_z
else 1
121 xrot
= (pi
* 2) * turn_camera
.camera_revol_x
* ix
122 yrot
= (pi
* 2) * turn_camera
.camera_revol_y
* iy
123 zrot
= (pi
* 2) * turn_camera
.camera_revol_z
* iz
125 # create middle frame
126 if turn_camera
.back_forw
is True:
127 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
128 myempty
.keyframe_insert(
129 data_path
='rotation_euler',
130 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
138 if turn_camera
.dolly_zoom
== "2":
139 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
140 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
142 frame
=((scene
.frame_end
- scene
.frame_start
) / 2)
146 myempty
.rotation_euler
= (xrot
, yrot
, zrot
)
147 myempty
.keyframe_insert(data_path
='rotation_euler', frame
=scene
.frame_end
)
149 if turn_camera
.dolly_zoom
!= "0":
150 if turn_camera
.dolly_zoom
== "1":
151 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_to_lens
# final
153 bpy
.data
.cameras
[camera
.name
].lens
= turn_camera
.camera_from_lens
# back to init
155 bpy
.data
.cameras
[camera
.name
].keyframe_insert(
156 'lens', frame
=scene
.frame_end
160 if turn_camera
.track
is True:
161 bpy
.context
.view_layer
.objects
.active
= camera
162 bpy
.ops
.object.constraint_add(type='TRACK_TO')
163 bpy
.context
.object.constraints
[-1].track_axis
= 'TRACK_NEGATIVE_Z'
164 bpy
.context
.object.constraints
[-1].up_axis
= 'UP_Y'
165 bpy
.context
.object.constraints
[-1].target
= bpy
.data
.objects
[myempty
.name
]
167 # back previous configuration
168 context
.preferences
.edit
.keyframe_new_interpolation_type
= savedinterpolation
169 bpy
.context
.scene
.cursor
.location
= savedcursor
171 # -------------------------
172 # Back to old selection
173 # -------------------------
174 bpy
.ops
.object.select_all(False)
175 selectobject
.select_set(True)
176 bpy
.context
.view_layer
.objects
.active
= selectobject
177 bpy
.context
.scene
.frame_set(savedframe
)
182 # ------------------------------------------------------
184 # ------------------------------------------------------
185 class CAMERATURN_Props(PropertyGroup
):
187 camera_revol_x
: FloatProperty(
188 name
='X', min=0, max=25,
189 default
=0, precision
=2,
190 description
='Number total of revolutions in X axis'
192 camera_revol_y
: FloatProperty(
193 name
='Y', min=0, max=25,
194 default
=0, precision
=2,
195 description
='Number total of revolutions in Y axis'
197 camera_revol_z
: FloatProperty(
198 name
='Z', min=0, max=25,
199 default
=1, precision
=2,
200 description
='Number total of revolutions in Z axis'
202 inverse_x
: BoolProperty(
204 description
="Inverse rotation",
207 inverse_y
: BoolProperty(
209 description
="Inverse rotation",
212 inverse_z
: BoolProperty(
214 description
="Inverse rotation",
217 use_cursor
: BoolProperty(
218 name
="Use cursor position",
219 description
="Use cursor position instead of object origin",
222 back_forw
: BoolProperty(
223 name
="Back and forward",
224 description
="Create back and forward animation",
227 dolly_zoom
: EnumProperty(
230 ('1', "Dolly zoom", ""),
231 ('2', "Dolly zoom B/F", "")
234 description
="Create a camera lens movement"
236 camera_from_lens
: FloatProperty(
238 min=1, max=500, default
=35,
240 description
="Start lens value"
242 camera_to_lens
: FloatProperty(
245 default
=35, precision
=3,
246 description
="End lens value"
249 name
="Create track constraint",
250 description
="Add a track constraint to the camera",
253 reset_cam_anim
: BoolProperty(
255 description
="Clear previous camera animations if there are any\n"
256 "(For instance, previous Dolly Zoom)",
261 # ------------------------------------------------------
263 # ------------------------------------------------------
264 class CAMERATURN_PT_ui(Panel
):
265 bl_idname
= "CAMERA_TURN_PT_main"
266 bl_label
= "Turnaround Camera"
267 bl_space_type
= "VIEW_3D"
268 bl_region_type
= "UI"
269 bl_category
= "Animate"
270 bl_context
= "objectmode"
271 bl_options
= {'DEFAULT_CLOSED'}
273 def draw(self
, context
):
275 scene
= context
.scene
276 turn_camera
= scene
.turn_camera
279 bpy
.context
.scene
.camera
.name
280 except AttributeError:
281 row
= layout
.row(align
=False)
282 row
.label(text
="No defined camera for scene", icon
="INFO")
285 if context
.active_object
is not None:
286 if context
.active_object
.type != 'CAMERA':
287 buf
= context
.active_object
.name
288 row
= layout
.row(align
=True)
289 row
.operator("object.rotate_around", icon
='OUTLINER_DATA_CAMERA')
292 box
.label(text
=buf
, icon
='MESH_DATA')
293 row
= layout
.row(align
=False)
294 row
.prop(scene
, "camera")
296 layout
.label(text
="Rotation:")
297 row
= layout
.row(align
=True)
298 row
.prop(scene
, "frame_start")
299 row
.prop(scene
, "frame_end")
301 col
= layout
.column(align
=True)
302 split
= col
.split(factor
=0.85, align
=True)
303 split
.prop(turn_camera
, "camera_revol_x")
304 split
.prop(turn_camera
, "inverse_x", toggle
=True)
305 split
= col
.split(factor
=0.85, align
=True)
306 split
.prop(turn_camera
, "camera_revol_y")
307 split
.prop(turn_camera
, "inverse_y", toggle
=True)
308 split
= col
.split(factor
=0.85, align
=True)
309 split
.prop(turn_camera
, "camera_revol_z")
310 split
.prop(turn_camera
, "inverse_z", toggle
=True)
312 col
= layout
.column(align
=True)
313 col
.label(text
="Options:")
314 row
= col
.row(align
=True)
315 row
.prop(turn_camera
, "back_forw", toggle
=True)
316 row
.prop(turn_camera
, "reset_cam_anim", toggle
=True)
317 col
.prop(turn_camera
, "track", toggle
=True)
318 col
.prop(turn_camera
, "use_cursor", toggle
=True)
321 row
.prop(turn_camera
, "dolly_zoom")
322 if turn_camera
.dolly_zoom
!= "0":
323 row
= layout
.row(align
=True)
324 row
.prop(turn_camera
, "camera_from_lens")
325 row
.prop(turn_camera
, "camera_to_lens")
328 buf
= "No valid object selected"
329 layout
.label(text
=buf
, icon
='MESH_DATA')
332 # ------------------------------------------------------
334 # ------------------------------------------------------
336 CAMERATURN_OT_RunAction
,
342 from bpy
.utils
import register_class
346 bpy
.types
.Scene
.turn_camera
= PointerProperty(type=CAMERATURN_Props
)
349 from bpy
.utils
import unregister_class
350 for cls
in reversed(classes
):
351 unregister_class(cls
)
353 del bpy
.types
.Scene
.turn_camera
356 if __name__
== "__main__":