camera_overscan.py 6.95 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

bl_info = {
    "name": "Camera Overscan",
    "author": "John Roper, Barnstorm VFX, Luca Scheller",
    "version": (1, 2, 1),
23
    "blender": (2, 80, 0),
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
    "location": "Render Settings > Camera Overscan",
    "description": "Render Overscan",
    "warning": "",
    "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Render/Camera_Overscan",
    "tracker_url": "",
    "category": "Render"}

import bpy
from bpy.types import (
        Operator,
        PropertyGroup,
        )
from bpy.props import (
        BoolProperty,
        IntProperty,
        FloatProperty,
        StringProperty,
        PointerProperty,
        )


class CODuplicateCamera(Operator):
    bl_idname = "scene.co_duplicate_camera"
    bl_label = "Bake to New Camera"
    bl_description = ("Make a new overscan camera with all the settings builtin\n"
                      "Needs an active Camera type in the Scene")

    @classmethod
    def poll(cls, context):
        active_cam = getattr(context.scene, "camera", None)
        return active_cam is not None

    def execute(self, context):
        active_cam = getattr(context.scene, "camera", None)
        try:
            if active_cam and active_cam.type == 'CAMERA':
                cam_obj = active_cam.copy()
                cam_obj.data = active_cam.data.copy()
                cam_obj.name = "Camera_Overscan"
63
                context.collection.objects.link(cam_obj)
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
        except:
            self.report({'WARNING'}, "Setting up a new Overscan Camera has failed")
            return {'CANCELLED'}

        return {'FINISHED'}


def RO_Update(self, context):
    scene = context.scene
    overscan = scene.camera_overscan
    render_settings = scene.render
    active_camera = getattr(scene, "camera", None)
    active_cam = getattr(active_camera, "data", None)

    # Check if there is a camera type in the scene (Object as camera doesn't work)
    if not active_cam or active_camera.type not in {'CAMERA'}:
        return None

    if overscan.RO_Activate:
        if overscan.RO_Safe_SensorSize == -1:
            # Safe Property Values
            overscan.RO_Safe_Res_X = render_settings.resolution_x
            overscan.RO_Safe_Res_Y = render_settings.resolution_y
            overscan.RO_Safe_SensorSize = active_cam.sensor_width
            overscan.RO_Safe_SensorFit = active_cam.sensor_fit

        if overscan.RO_Custom_Res_X == 0 or overscan.RO_Custom_Res_Y == 0:
            # avoid infinite recursion on props update
            if overscan.RO_Custom_Res_X != render_settings.resolution_x:
                overscan.RO_Custom_Res_X = render_settings.resolution_x
            if overscan.RO_Custom_Res_Y != render_settings.resolution_y:
                overscan.RO_Custom_Res_Y = render_settings.resolution_y

        # Reset Property Values
        active_cam.sensor_width = scene.camera_overscan.RO_Safe_SensorSize

        # Calc Sensor Size
        active_cam.sensor_fit = 'HORIZONTAL'
        sensor_size_factor = overscan.RO_Custom_Res_X / overscan.RO_Safe_Res_X
        Old_SensorSize = active_cam.sensor_width
        New_SensorSize = Old_SensorSize * sensor_size_factor

        # Set New Property Values
        active_cam.sensor_width = New_SensorSize
        render_settings.resolution_x = overscan.RO_Custom_Res_X
        render_settings.resolution_y = overscan.RO_Custom_Res_Y

    else:
        if overscan.RO_Safe_SensorSize != -1:
            # Set Property Values
            render_settings.resolution_x = overscan.RO_Safe_Res_X
            render_settings.resolution_y = overscan.RO_Safe_Res_Y
            active_cam.sensor_width = overscan.RO_Safe_SensorSize
            active_cam.sensor_fit = overscan.RO_Safe_SensorFit
            overscan.RO_Safe_SensorSize = -1


def RO_Menu(self, context):
    scene = context.scene
    overscan = scene.camera_overscan
    layout = self.layout
    row = layout.row()
    active_cam = getattr(scene, "camera", None)

    if active_cam and active_cam.type == 'CAMERA':
        row.prop(overscan, 'RO_Activate', text="Use Overscan")
        row_enable = row.row(align=True)
        if not overscan.RO_Activate:
            row_enable.enabled = False
        row_enable.prop(overscan, 'RO_Custom_Res_X', text="X")
        row_enable.prop(overscan, 'RO_Custom_Res_Y', text="Y")
        row = layout.row()
        if not overscan.RO_Activate:
            row.enabled = False
        row.operator("scene.co_duplicate_camera", icon="RENDER_STILL")
    else:
140
        row.label(text="No active Camera type in the Scene", icon='INFO')
141 142 143


class camera_overscan_props(PropertyGroup):
144
    RO_Activate: BoolProperty(
145 146 147 148 149 150
                        default=False,
                        description="Enable/Disable Camera Overscan\n"
                                    "Affects the active Scene Camera only\n"
                                    "(Objects as cameras are not supported)",
                        update=RO_Update
                        )
151
    RO_Custom_Res_X: IntProperty(
152 153 154 155 156
                        default=0,
                        min=0,
                        max=65536,
                        update=RO_Update
                        )
157
    RO_Custom_Res_Y: IntProperty(
158 159 160 161 162
                        default=0,
                        min=0,
                        max=65536,
                        update=RO_Update
                        )
163 164
    RO_Safe_Res_X: FloatProperty()
    RO_Safe_Res_Y: FloatProperty()
165 166

    # the hard limit is sys.max which is too much, used 65536 instead
167
    RO_Safe_SensorSize: FloatProperty(
168 169 170 171
                        default=-1,
                        min=-1,
                        max=65536
                        )
172
    RO_Safe_SensorFit: StringProperty()
173 174 175


def register():
176 177
    bpy.utils.register_class(CODuplicateCamera)
    bpy.utils.register_class(camera_overscan_props)
178 179 180 181 182 183 184
    bpy.types.RENDER_PT_dimensions.append(RO_Menu)
    bpy.types.Scene.camera_overscan = PointerProperty(
                                        type=camera_overscan_props
                                        )


def unregister():
185 186
    bpy.utils.unregister_class(CODuplicateCamera)
    bpy.utils.unregister_class(camera_overscan_props)
187 188 189 190 191 192
    bpy.types.RENDER_PT_dimensions.remove(RO_Menu)
    del bpy.types.Scene.camera_overscan


if __name__ == "__main__":
    register()