Skip to content
Snippets Groups Projects
camera_overscan.py 7.01 KiB
# ##### 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, 2),
    "blender": (2, 80, 0),
    "location": "Render Settings > Camera Overscan",
    "description": "Render Overscan",
    "warning": "",
    "doc_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"
                context.collection.objects.link(cam_obj)
        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
    active_cam = getattr(scene, "camera", None)
    layout = self.layout

    if active_cam and active_cam.type == 'CAMERA':
        col = layout.column(align=True)
        col.prop(overscan, "RO_Activate", text="Use Overscan")

        sub = col.column(align=True)
        sub.active = overscan.RO_Activate
        sub.prop(overscan, "RO_Custom_Res_X", text="Overscan X")
        sub.prop(overscan, "RO_Custom_Res_Y", text="Y")

        col = layout.column(align=True)
        col.active = overscan.RO_Activate
        col.operator("scene.co_duplicate_camera", icon="RENDER_STILL")
    else:
        col = layout.column()
        col.label(text="No active Camera type in the Scene", icon='INFO')


class camera_overscan_props(PropertyGroup):
    RO_Activate: BoolProperty(
                        default=False,
                        description="Enable/Disable Camera Overscan\n"
                                    "Affects the active Scene Camera only\n"
                                    "(Objects as cameras are not supported)",
                        update=RO_Update
                        )
    RO_Custom_Res_X: IntProperty(
                        default=0,
                        min=4,
                        max=65536,
                        subtype='PIXEL',
                        update=RO_Update
                        )
    RO_Custom_Res_Y: IntProperty(
                        default=0,
                        min=4,
                        max=65536,
                        subtype='PIXEL',
                        update=RO_Update
                        )
    RO_Safe_Res_X: FloatProperty()
    RO_Safe_Res_Y: FloatProperty()

    # the hard limit is sys.max which is too much, used 65536 instead
    RO_Safe_SensorSize: FloatProperty(
                        default=-1,
                        min=-1,
                        max=65536
                        )
    RO_Safe_SensorFit: StringProperty()


def register():
    bpy.utils.register_class(CODuplicateCamera)
    bpy.utils.register_class(camera_overscan_props)
    bpy.types.RENDER_PT_format.append(RO_Menu)
    bpy.types.Scene.camera_overscan = PointerProperty(
                                        type=camera_overscan_props
                                        )


def unregister():
    bpy.utils.unregister_class(CODuplicateCamera)
    bpy.utils.unregister_class(camera_overscan_props)
    bpy.types.RENDER_PT_format.remove(RO_Menu)
    del bpy.types.Scene.camera_overscan


if __name__ == "__main__":
    register()