diff --git a/src/scenex/model/_nodes/camera.py b/src/scenex/model/_nodes/camera.py index 31bec776..308f61cc 100644 --- a/src/scenex/model/_nodes/camera.py +++ b/src/scenex/model/_nodes/camera.py @@ -2,11 +2,11 @@ import math from abc import abstractmethod -from typing import TYPE_CHECKING, Annotated, Any, Literal, Union +from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal, Union import numpy as np import pylinalg as la -from pydantic import Field, PrivateAttr +from pydantic import BaseModel, ConfigDict, Field, PrivateAttr from scenex.app.events import ( MouseButton, @@ -15,7 +15,6 @@ MousePressEvent, WheelEvent, ) -from scenex.model._base import EventedBase from scenex.utils import projections from .node import Node @@ -176,7 +175,7 @@ def look_at(self, target: Position3D, /, *, up: Vector3D | None = None) -> None: # ==================================================================================== -class CameraController(EventedBase): +class CameraController(BaseModel): """Base class defining how a camera responds to user interaction events. A CameraController handles user input (mouse, keyboard, wheel) to manipulate @@ -203,6 +202,11 @@ class CameraController(EventedBase): Camera : Camera class that uses controllers """ + model_config: ClassVar[ConfigDict] = ConfigDict( + validate_default=True, + validate_assignment=True, + ) + @abstractmethod def handle_event(self, event: Event, view: View) -> bool: """ @@ -268,12 +272,13 @@ class PanZoom(CameraController): Create an image viewer with pan/zoom: >>> import numpy as np + >>> import scenex as snx >>> from scenex.utils import projections >>> my_data = np.random.rand(512, 512).astype(np.float32) - >>> view = View( - ... scene=Scene(children=[Image(data=my_data)]), - ... camera=Camera( - ... controller=PanZoom(), + >>> view = snx.View( + ... scene=snx.Scene(children=[snx.Image(data=my_data)]), + ... camera=snx.Camera( + ... controller=snx.PanZoom(), ... interactive=True, ... ), ... ) @@ -403,39 +408,42 @@ class Orbit(CameraController): Examples -------- Orbit around the origin: + >>> import scenex as snx >>> from scenex.utils import projections >>> # Create a perspective camera... - >>> camera = Camera( + >>> camera = snx.Camera( ... interactive=True, ... projection=projections.perspective(fov=70, near=1, far=1000), ... ) >>> # ...positioned along the X axis... - >>> camera.transform = Transform().translated((100, 0, 0)) + >>> camera.transform = snx.Transform().translated((100, 0, 0)) >>> # ...looking at the origin... >>> camera.look_at((0, 0, 0), up=(0, 0, 1)) >>> # ...that orbits around the origin - >>> camera.controller = Orbit(center=(0, 0, 0)) + >>> camera.controller = snx.Orbit(center=(0, 0, 0)) Orbit around a data volume's center: >>> import numpy as np >>> my_data = np.random.rand(100, 100, 100).astype(np.float32) - >>> volume = Volume(data=my_data) + >>> volume = snx.Volume(data=my_data) >>> center = np.mean(volume.bounding_box, axis=0) >>> # Create a perspective camera... - >>> camera = Camera( + >>> camera = snx.Camera( ... interactive=True, ... projection=projections.perspective(fov=70, near=1, far=1000), ... ) >>> # ...positioned along the X axis from the volume center... - >>> camera.transform = Transform().translated(center).translated((100, 0, 0)) + >>> camera.transform = ( + ... snx.Transform().translated(center).translated((100, 0, 0)) + ... ) >>> # ...looking at the center... >>> camera.look_at(center, up=(0, 0, 1)) >>> # ...that orbits around the center - >>> camera.controller = Orbit(center=center) + >>> camera.controller = snx.Orbit(center=center) Custom polar axis for Y-up scenes: - >>> camera = Camera( - ... controller=Orbit(center=(0, 0, 0), polar_axis=(0, 1, 0)), + >>> camera = snx.Camera( + ... controller=snx.Orbit(center=(0, 0, 0), polar_axis=(0, 1, 0)), ... interactive=True, ... )