import bpy
import numpy as np
import mathutils
from .utils import handle_vec, SelectObjects, animatable_property
from .bsyn_object import BsynObject
from typing import Union
from .mesh import Mesh
from .curve import Curve
from ..utils import types
def _look_at_rotation(
obj_camera: bpy.types.Object,
at: mathutils.Vector = mathutils.Vector((0, 0, 0)),
up: mathutils.Vector = mathutils.Vector((0, 0, 1)),
) -> mathutils.Euler:
"""Rotate camera to look at 'at', with 'up' maintained.
:param obj_camera: Camera object
:param at: Point to look at
:param up: Up vector"""
camera_position = obj_camera.location
z_axis = -(
at - camera_position
).normalized() # Blender forwards is '-z', so use -ve here
x_axis = up.cross(z_axis)
y_axis = z_axis.cross(x_axis).normalized()
is_close = all(abs(i) < 5e-3 for i in x_axis)
if is_close:
replacement = y_axis.cross(z_axis).normalized()
x_axis = replacement
R = mathutils.Matrix([x_axis, y_axis, z_axis]).transposed()
euler = R.to_euler()
return euler
[docs]
class Camera(BsynObject):
"""Camera object, to handle movement, tracking, etc."""
def __init__(self, camera: bpy.types.Object = None):
if camera is None:
camera = bpy.context.scene.camera
self.camera = camera
self._object = camera
[docs]
@classmethod
def create(
cls,
name: str = "Camera",
location: Union[list, tuple, np.ndarray] = None,
rotation: Union[list, tuple, np.ndarray] = None,
) -> "Camera":
"""Create a new camera object.
:param name: Name of camera
:param location: Location of camera
:param rotation_euler: Rotation of camera in euler angles"""
with SelectObjects(): # revert selection after creating camera
bpy.ops.object.camera_add(enter_editmode=False, align="VIEW")
camera = bpy.context.object
camera.name = name
cam = cls(camera)
if location is not None:
cam.set_location(location)
if rotation is not None:
cam.set_rotation_euler(rotation)
return cam
[docs]
@classmethod
def from_scene(cls, name: str = "Camera") -> "Camera":
"""Create from a named camera in scene
:param name: Name of camera"""
assert name in bpy.data.objects, f"Camera {name} does not exist"
return cls(bpy.data.objects[name])
[docs]
def update(self):
"""Update view layer and depsgraph"""
bpy.context.view_layer.update()
bpy.context.evaluated_depsgraph_get()
@property
def fov(self):
"""Field of view in degrees"""
return self.camera.data.angle * 180 / np.pi
@fov.setter
def fov(self, fov):
self.set_fov(fov)
# FOV is not animatable, so keyframe focal length instead
@animatable_property("lens", "data")
def set_fov(self, fov):
self.camera.data.angle = fov * np.pi / 180
self.update()
@property
def focal_length(self):
"""Focal length in mm"""
return self.camera.data.lens
@focal_length.setter
def focal_length(self, focal_length):
self.set_focal_length(focal_length)
@animatable_property("lens", "data")
def set_focal_length(self, focal_length):
self.camera.data.lens = focal_length
self.update()
@property
def clip_start(self):
return self.camera.data.clip_start
@clip_start.setter
def clip_start(self, clip_start):
self.set_clip_start(clip_start)
@animatable_property("clip_start", "data")
def set_clip_start(self, clip_start):
self.camera.data.clip_start = clip_start
self.update()
@property
def clip_end(self):
return self.camera.data.clip_end
@clip_end.setter
def clip_end(self, clip_end):
self.set_clip_end(clip_end)
@animatable_property("clip_end", "data")
def set_clip_end(self, clip_end):
self.camera.data.clip_end = clip_end
self.update()
[docs]
def look_at(
self,
at: types.VectorLike = mathutils.Vector((0, 0, 0)),
up: types.VectorLike = mathutils.Vector((0, 0, 1)),
):
"""Look at a point in space, with up vector.
:param at: Point to look at
:param up: Up vector"""
at = handle_vec(at, 3)
up = handle_vec(up, 3)
self.rotation_euler = _look_at_rotation(self.camera, at, up)
[docs]
def look_at_object(
self,
obj: Union[Mesh, bpy.types.Object],
up: types.VectorLike = mathutils.Vector((0, 0, 1)),
):
"""Look at an object, with up vector.
:param obj: Object to look at
:param up: Up vector"""
if isinstance(obj, Mesh):
obj = obj.obj
self.look_at(obj.location, up)
def place_and_rotate(self, pos, euler):
self.location = pos
self.euler = euler
def place_and_look_at(self, pos, at, up=mathutils.Vector((0, 0, 1))):
pos = handle_vec(pos)
at = handle_vec(at)
up = handle_vec(up)
self.location = pos
self.look_at(at, up)