|
@@ -1,6 +1,6 @@
|
|
import asyncio
|
|
import asyncio
|
|
from dataclasses import dataclass
|
|
from dataclasses import dataclass
|
|
-from typing import Any, Callable, Dict, List, Optional, Union
|
|
|
|
|
|
+from typing import Any, Callable, Dict, List, Literal, Optional, Union
|
|
|
|
|
|
from typing_extensions import Self
|
|
from typing_extensions import Self
|
|
|
|
|
|
@@ -20,6 +20,8 @@ from .scene_object3d import Object3D
|
|
|
|
|
|
@dataclass(**KWONLY_SLOTS)
|
|
@dataclass(**KWONLY_SLOTS)
|
|
class SceneCamera:
|
|
class SceneCamera:
|
|
|
|
+ type: Literal['perspective', 'orthographic']
|
|
|
|
+ params: Dict[str, float]
|
|
x: float = 0
|
|
x: float = 0
|
|
y: float = -3
|
|
y: float = -3
|
|
z: float = 5
|
|
z: float = 5
|
|
@@ -71,6 +73,7 @@ class Scene(Element,
|
|
width: int = 400,
|
|
width: int = 400,
|
|
height: int = 300,
|
|
height: int = 300,
|
|
grid: bool = True,
|
|
grid: bool = True,
|
|
|
|
+ camera: Optional[SceneCamera] = None,
|
|
on_click: Optional[Callable[..., Any]] = None,
|
|
on_click: Optional[Callable[..., Any]] = None,
|
|
on_drag_start: Optional[Callable[..., Any]] = None,
|
|
on_drag_start: Optional[Callable[..., Any]] = None,
|
|
on_drag_end: Optional[Callable[..., Any]] = None,
|
|
on_drag_end: Optional[Callable[..., Any]] = None,
|
|
@@ -86,6 +89,7 @@ class Scene(Element,
|
|
:param width: width of the canvas
|
|
:param width: width of the canvas
|
|
:param height: height of the canvas
|
|
:param height: height of the canvas
|
|
:param grid: whether to display a grid
|
|
:param grid: whether to display a grid
|
|
|
|
+ :param camera: camera definition, either instance of ``ui.scene.perspective_camera`` (default) or ``ui.scene.orthographic_camera``
|
|
:param on_click: callback to execute when a 3D object is clicked
|
|
:param on_click: callback to execute when a 3D object is clicked
|
|
:param on_drag_start: callback to execute when a 3D object is dragged
|
|
:param on_drag_start: callback to execute when a 3D object is dragged
|
|
:param on_drag_end: callback to execute when a 3D object is dropped
|
|
:param on_drag_end: callback to execute when a 3D object is dropped
|
|
@@ -95,10 +99,11 @@ class Scene(Element,
|
|
self._props['width'] = width
|
|
self._props['width'] = width
|
|
self._props['height'] = height
|
|
self._props['height'] = height
|
|
self._props['grid'] = grid
|
|
self._props['grid'] = grid
|
|
- self._props['camera_type'] = 'perspective'
|
|
|
|
|
|
+ self.camera = camera or self.perspective_camera()
|
|
|
|
+ self._props['camera_type'] = self.camera.type
|
|
|
|
+ self._props['camera_params'] = self.camera.params
|
|
self.objects: Dict[str, Object3D] = {}
|
|
self.objects: Dict[str, Object3D] = {}
|
|
self.stack: List[Union[Object3D, SceneObject]] = [SceneObject()]
|
|
self.stack: List[Union[Object3D, SceneObject]] = [SceneObject()]
|
|
- self.camera: SceneCamera = SceneCamera()
|
|
|
|
self._click_handlers = [on_click] if on_click else []
|
|
self._click_handlers = [on_click] if on_click else []
|
|
self._drag_start_handlers = [on_drag_start] if on_drag_start else []
|
|
self._drag_start_handlers = [on_drag_start] if on_drag_start else []
|
|
self._drag_end_handlers = [on_drag_end] if on_drag_end else []
|
|
self._drag_end_handlers = [on_drag_end] if on_drag_end else []
|
|
@@ -124,6 +129,29 @@ class Scene(Element,
|
|
self._drag_end_handlers.append(callback)
|
|
self._drag_end_handlers.append(callback)
|
|
return self
|
|
return self
|
|
|
|
|
|
|
|
+ @staticmethod
|
|
|
|
+ def perspective_camera(*, fov: float = 75, near: float = 0.1, far: float = 1000) -> SceneCamera:
|
|
|
|
+ """Create a perspective camera.
|
|
|
|
+
|
|
|
|
+ :param fov: vertical field of view in degrees
|
|
|
|
+ :param near: near clipping plane
|
|
|
|
+ :param far: far clipping plane
|
|
|
|
+ """
|
|
|
|
+ return SceneCamera(type='perspective', params={'fov': fov, 'near': near, 'far': far})
|
|
|
|
+
|
|
|
|
+ @staticmethod
|
|
|
|
+ def orthographic_camera(*, size: float = 10, near: float = 0.1, far: float = 1000) -> SceneCamera:
|
|
|
|
+ """Create a orthographic camera.
|
|
|
|
+
|
|
|
|
+ The size defines the vertical size of the view volume, i.e. the distance between the top and bottom clipping planes.
|
|
|
|
+ The left and right clipping planes are set such that the aspect ratio matches the viewport.
|
|
|
|
+
|
|
|
|
+ :param size: vertical size of the view volume
|
|
|
|
+ :param near: near clipping plane
|
|
|
|
+ :param far: far clipping plane
|
|
|
|
+ """
|
|
|
|
+ return SceneCamera(type='orthographic', params={'size': size, 'near': near, 'far': far})
|
|
|
|
+
|
|
def __enter__(self) -> Self:
|
|
def __enter__(self) -> Self:
|
|
Object3D.current_scene = self
|
|
Object3D.current_scene = self
|
|
super().__enter__()
|
|
super().__enter__()
|