Skip to content
Snippets Groups Projects
camera_turnaround.py 11.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0-or-later
    
    
    bl_info = {
        "name": "Turnaround Camera",
        "author": "Antonio Vazquez (antonioya)",
    
    Antonioya's avatar
    Antonioya committed
        "version": (0, 3, 0),
        "blender": (2, 80, 0),
    
        "location": "View3D > Sidebar > View Tab > Turnaround Camera",
    
        "description": "Add a camera rotation around selected object",
    
        "doc_url": "{BLENDER_MANUAL_URL}/addons/animation/turnaround_camera.html",
    
    from math import pi
    from bpy.props import (
    
        BoolProperty,
        EnumProperty,
        FloatProperty,
        PointerProperty,
    )
    
    from bpy.types import (
    
        Operator,
        Panel,
        PropertyGroup,
    )
    
    # ------------------------------------------------------
    # Action class
    # ------------------------------------------------------
    
    Antonioya's avatar
    Antonioya committed
    class CAMERATURN_OT_RunAction(Operator):
    
        bl_idname = "object.rotate_around"
        bl_label = "Turnaround"
        bl_description = "Create camera rotation around selected object"
    
        bl_options = {'REGISTER', 'UNDO'}
    
    
        def execute(self, context):
            # ----------------------
            # Save old data
            # ----------------------
            scene = context.scene
    
            turn_camera = scene.turn_camera
    
            selectobject = context.active_object
    
            camera = scene.camera
            savedcursor = scene.cursor.location.copy()  # cursor position
    
            savedframe = scene.frame_current
    
            if turn_camera.use_cursor is False:
    
                bpy.ops.view3d.snap_cursor_to_selected()
    
            # -------------------------
            # Create empty and parent
            # -------------------------
            bpy.ops.object.empty_add(type='PLAIN_AXES')
    
            myempty = context.active_object
    
    
            myempty.location = selectobject.location
            savedstate = myempty.matrix_world
            myempty.parent = selectobject
    
            myempty.name = "MCH_Rotation_target"
    
            myempty.matrix_world = savedstate
    
            # -------------------------
            # Parent camera to empty
            # -------------------------
            savedstate = camera.matrix_world
            camera.parent = myempty
            camera.matrix_world = savedstate
    
            # -------------------------
            # Now add revolutions
            # (make empty active object)
            # -------------------------
            bpy.ops.object.select_all(False)
    
            myempty.select_set(True)
    
            context.view_layer.objects.active = myempty
    
            # save current configuration
    
            savedinterpolation = context.preferences.edit.keyframe_new_interpolation_type
    
            # change interpolation mode
    
            context.preferences.edit.keyframe_new_interpolation_type = 'LINEAR'
    
            # create first frame
            myempty.rotation_euler = (0, 0, 0)
    
            myempty.empty_display_size = 0.1
    
            scene.frame_set(scene.frame_start)
    
            myempty.keyframe_insert(data_path="rotation_euler", frame=scene.frame_start)
    
    
            # Clear the Camera Animations if the option is checked
            if turn_camera.reset_cam_anim:
                try:
                    if bpy.data.cameras[camera.name].animation_data:
                        bpy.data.cameras[camera.name].animation_data_clear()
                except Exception as e:
                    print("\n[Camera Turnaround]\nWarning: {}\n".format(e))
    
    
            if turn_camera.dolly_zoom != "0":
                bpy.data.cameras[camera.name].lens = turn_camera.camera_from_lens
    
                bpy.data.cameras[camera.name].keyframe_insert("lens", frame=scene.frame_start)
    
    
            # Calculate rotation XYZ
    
            ix = -1 if turn_camera.inverse_x else 1
            iy = -1 if turn_camera.inverse_y else 1
            iz = -1 if turn_camera.inverse_z else 1
    
            xrot = (pi * 2) * turn_camera.camera_revol_x * ix
            yrot = (pi * 2) * turn_camera.camera_revol_y * iy
            zrot = (pi * 2) * turn_camera.camera_revol_z * iz
    
    
            # create middle frame
    
            if turn_camera.back_forw is True:
    
                myempty.rotation_euler = (xrot, yrot, zrot)
    
                myempty.keyframe_insert(
    
                    data_path="rotation_euler",
                    frame=((scene.frame_end - scene.frame_start) / 2)
                )
    
                # reverse
                xrot *= -1
                yrot *= -1
                zrot = 0
    
            # Dolly zoom
    
            if turn_camera.dolly_zoom == "2":
                bpy.data.cameras[camera.name].lens = turn_camera.camera_to_lens
                bpy.data.cameras[camera.name].keyframe_insert(
    
                    "lens",
                    frame=((scene.frame_end - scene.frame_start) / 2)
                )
    
    
            # create last frame
            myempty.rotation_euler = (xrot, yrot, zrot)
    
            myempty.keyframe_insert(data_path="rotation_euler", frame=scene.frame_end)
    
            if turn_camera.dolly_zoom != "0":
                if turn_camera.dolly_zoom == "1":
                    bpy.data.cameras[camera.name].lens = turn_camera.camera_to_lens  # final
    
                    bpy.data.cameras[camera.name].lens = turn_camera.camera_from_lens  # back to init
    
                bpy.data.cameras[camera.name].keyframe_insert(
    
                    "lens", frame=scene.frame_end
                )
    
            if turn_camera.track is True:
    
                bpy.context.view_layer.objects.active = camera
    
                bpy.ops.object.constraint_add(type='TRACK_TO')
                bpy.context.object.constraints[-1].track_axis = 'TRACK_NEGATIVE_Z'
                bpy.context.object.constraints[-1].up_axis = 'UP_Y'
    
                bpy.context.object.constraints[-1].target = myempty
    
    
            # back previous configuration
    
            context.preferences.edit.keyframe_new_interpolation_type = savedinterpolation
    
            scene.cursor.location = savedcursor
    
    
            # -------------------------
            # Back to old selection
            # -------------------------
            bpy.ops.object.select_all(False)
    
            selectobject.select_set(True)
    
            bpy.context.view_layer.objects.active = selectobject
    
    # ------------------------------------------------------
    
    # Define Properties
    
    # ------------------------------------------------------
    
    Antonioya's avatar
    Antonioya committed
    class CAMERATURN_Props(PropertyGroup):
    
        camera_revol_x: FloatProperty(
    
            name="X", min=0, max=25,
            default=0, precision=2,
            description="Number total of revolutions in X axis"
        )
    
        camera_revol_y: FloatProperty(
    
            name="Y", min=0, max=25,
            default=0, precision=2,
            description="Number total of revolutions in Y axis"
        )
    
        camera_revol_z: FloatProperty(
    
            name="Z", min=0, max=25,
            default=1, precision=2,
            description="Number total of revolutions in Z axis"
        )
    
        inverse_x: BoolProperty(
    
            name="-X",
            description="Inverse rotation",
            default=False
        )
    
        inverse_y: BoolProperty(
    
            name="-Y",
            description="Inverse rotation",
            default=False
        )
    
        inverse_z: BoolProperty(
    
            name="-Z",
            description="Inverse rotation",
            default=False
        )
    
        use_cursor: BoolProperty(
    
            name="Use cursor position",
            description="Use cursor position instead of object origin",
            default=False
        )
    
        back_forw: BoolProperty(
    
            name="Back and forward",
            description="Create back and forward animation",
            default=False
        )
    
        dolly_zoom: EnumProperty(
    
            items=(
                ('0', "None", ""),
                ('1', "Dolly zoom", ""),
                ('2', "Dolly zoom B/F", "")
            ),
            name="Lens Effects",
            description="Create a camera lens movement"
        )
    
        camera_from_lens: FloatProperty(
    
            name="From",
            min=1, max=500, default=35,
            precision=3,
            description="Start lens value"
        )
    
        camera_to_lens: FloatProperty(
    
            name="To",
            min=1, max=500,
            default=35, precision=3,
            description="End lens value"
        )
    
        track: BoolProperty(
    
            name="Create track constraint",
            description="Add a track constraint to the camera",
            default=False
        )
    
        reset_cam_anim: BoolProperty(
    
            name="Clear Camera",
            description="Clear previous camera animations if there are any\n"
            "(For instance, previous Dolly Zoom)",
            default=False
        )
    
    # ------------------------------------------------------
    # UI Class
    # ------------------------------------------------------
    
    Antonioya's avatar
    Antonioya committed
    class CAMERATURN_PT_ui(Panel):
    
        bl_idname = "CAMERA_TURN_PT_main"
    
        bl_label = "Turnaround Camera"
        bl_space_type = "VIEW_3D"
    
    Antonioya's avatar
    Antonioya committed
        bl_region_type = "UI"
    
        bl_category = "Animate"
    
        bl_context = "objectmode"
    
        bl_options = {'DEFAULT_CLOSED'}
    
    
        def draw(self, context):
            layout = self.layout
            scene = context.scene
    
            turn_camera = scene.turn_camera
    
    
            except AttributeError:
                row = layout.row(align=False)
    
                row.label(text="No defined camera for scene", icon="INFO")
    
                return
    
            if context.active_object is not None:
                if context.active_object.type != 'CAMERA':
                    buf = context.active_object.name
    
                    row = layout.row(align=True)
    
                    row.operator("object.rotate_around", icon='OUTLINER_DATA_CAMERA')
    
                    box = row.box()
                    box.scale_y = 0.5
    
                    box.label(text=buf, icon='MESH_DATA')
    
                    row = layout.row(align=False)
                    row.prop(scene, "camera")
    
                    layout.label(text="Rotation:")
    
                    row = layout.row(align=True)
    
                    row.prop(scene, "frame_start")
                    row.prop(scene, "frame_end")
    
    
                    col = layout.column(align=True)
    
    Antonioya's avatar
    Antonioya committed
                    split = col.split(factor=0.85, align=True)
    
                    split.prop(turn_camera, "camera_revol_x")
                    split.prop(turn_camera, "inverse_x", toggle=True)
    
    Antonioya's avatar
    Antonioya committed
                    split = col.split(factor=0.85, align=True)
    
                    split.prop(turn_camera, "camera_revol_y")
                    split.prop(turn_camera, "inverse_y", toggle=True)
    
    Antonioya's avatar
    Antonioya committed
                    split = col.split(factor=0.85, align=True)
    
                    split.prop(turn_camera, "camera_revol_z")
                    split.prop(turn_camera, "inverse_z", toggle=True)
    
                    col = layout.column(align=True)
    
                    col.label(text="Options:")
    
                    row = col.row(align=True)
                    row.prop(turn_camera, "back_forw", toggle=True)
                    row.prop(turn_camera, "reset_cam_anim", toggle=True)
                    col.prop(turn_camera, "track", toggle=True)
                    col.prop(turn_camera, "use_cursor", toggle=True)
    
    
                    row = layout.row()
    
                    row.prop(turn_camera, "dolly_zoom")
                    if turn_camera.dolly_zoom != "0":
                        row = layout.row(align=True)
                        row.prop(turn_camera, "camera_from_lens")
                        row.prop(turn_camera, "camera_to_lens")
    
    
                else:
                    buf = "No valid object selected"
    
                    layout.label(text=buf, icon='MESH_DATA')
    
    
    
    # ------------------------------------------------------
    # Registration
    # ------------------------------------------------------
    
    Antonioya's avatar
    Antonioya committed
    classes = (
        CAMERATURN_OT_RunAction,
        CAMERATURN_PT_ui,
        CAMERATURN_Props
    )
    
    
    def register():
    
    Antonioya's avatar
    Antonioya committed
        from bpy.utils import register_class
        for cls in classes:
            register_class(cls)
    
    Antonioya's avatar
    Antonioya committed
        bpy.types.Scene.turn_camera = PointerProperty(type=CAMERATURN_Props)
    
    def unregister():
    
    Antonioya's avatar
    Antonioya committed
        from bpy.utils import unregister_class
        for cls in reversed(classes):
            unregister_class(cls)
    
    
        del bpy.types.Scene.turn_camera
    
    
    
    if __name__ == "__main__":
        register()