Skip to content

Commit

Permalink
feat: add camera
Browse files Browse the repository at this point in the history
  • Loading branch information
hanson-hschang committed Sep 7, 2024
1 parent 714e44e commit 6e3bfbd
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/bsr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
scene_update,
select_camera,
)
from .camera import CameraManager
from .frame import FrameManager
from .geometry.composite.rod import Rod
from .geometry.composite.stack import RodStack, create_rod_collection
Expand All @@ -30,4 +31,5 @@ def get_version() -> str:


version: Final[str] = get_version()
camera_manager = CameraManager()
frame_manager = FrameManager()
152 changes: 152 additions & 0 deletions src/bsr/camera.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
from typing import Optional

import bpy
import numpy as np

from .utilities.singleton import SingletonMeta


class CameraManager(metaclass=SingletonMeta):
"""
This class provides methods for manipulating the camera of the scene.
Only one instance exist, which you can access by: bsr.camera_manager.
"""

def __init__(self) -> None:
"""
Constructor for camera manager.
"""
self.__look_at_location = None
self.__sky = np.array([0, 0, 1])

@property
def camera(self) -> bpy.types.Object:
"""
Return the camera object.
"""
return bpy.data.objects["Camera"]

def set_film_transparent(self, transparent: bool = True) -> None:
"""
Set the film transparency for rendering.
Parameters
----------
transparent : bool, optional
Whether the film should be transparent. Default is True.
"""
bpy.context.scene.render.film_transparent = transparent

@property
def is_film_transparent(self) -> bool:
"""
Check if the film is set to transparent.
Returns
-------
bool
True if the film is transparent, False otherwise.
"""
return bpy.context.scene.render.film_transparent

@property
def location(self) -> np.ndarray:
"""
Return the current location of the camera.
"""
return np.array(self.camera.location)

@location.setter
def location(self, location: np.ndarray) -> None:
"""
Set the location of the camera. If the look at location is set, the camera will be rotated to look at that location.
Parameters
----------
location : np.array
The location of the camera.
"""
assert isinstance(
location, np.ndarray
), "location must be a numpy array"
assert len(location) == 3, "location must have 3 elements"
self.camera.location = location
if self.__look_at_location is not None:
self.camera.matrix_world = self.compute_matrix_world(
location=self.location,
direction=self.__look_at_location - self.location,
sky=self.__sky,
)

@property
def look_at(self) -> Optional[np.ndarray]:
"""
Return the location the camera is looking at.
"""
return self.__look_at_location

@look_at.setter
def look_at(self, location: np.ndarray) -> None:
"""
Set the direction the camera is looking at.
Parameters
----------
location : np.array
The direction the camera is looking at.
"""
assert isinstance(
location, np.ndarray
), "location must be a numpy array"
assert len(location) == 3, "location must have 3 elements"
assert (
np.allclose(self.location, location) == False
), "camera and look at location must be different"

self.__look_at_location = location
self.camera.matrix_world = self.compute_matrix_world(
location=self.location,
direction=self.__look_at_location - self.location,
sky=self.__sky,
)

@staticmethod
def compute_matrix_world(
location: np.ndarray, direction: np.ndarray, sky: np.ndarray
) -> np.ndarray:
"""
Compute the world matrix of the camera.
Parameters
----------
location : np.array
The location of the camera.
direction : np.array
The direction the camera is looking at.
sky : np.array
The sky direction of the camera. (unit vector)
Returns
-------
np.array
The world matrix of the camera.
"""
assert isinstance(
location, np.ndarray
), "location must be a numpy array"
assert len(location) == 3, "location must have 3 elements"
assert isinstance(
direction, np.ndarray
), "direction must be a numpy array"
assert len(direction) == 3, "direction must have 3 elements"
assert isinstance(sky, np.ndarray), "sky must be a numpy array"
assert len(sky) == 3, "sky must have 3 elements"
assert np.linalg.norm(sky) == 1, "sky must be a unit vector"

direction = direction / np.linalg.norm(direction)
right = np.cross(direction, sky)
up = np.cross(right, direction)

return np.array(
[[*right, 0], [*up, 0], [*(-direction), 0], [*location, 1]]
)

0 comments on commit 6e3bfbd

Please sign in to comment.