ソースを参照

Introduce camera parameters for `ui.scene` (#2897)

* introduce camera parameters for `ui.scene`

* code review
Falko Schindler 1 年間 前
コミット
e13cd6313e

+ 18 - 6
nicegui/elements/scene.js

@@ -65,13 +65,24 @@ export default {
 
 
     window["scene_" + this.$el.id] = this.scene; // NOTE: for selenium tests only
     window["scene_" + this.$el.id] = this.scene; // NOTE: for selenium tests only
 
 
-    this.look_at = new THREE.Vector3(0, 0, 0);
-    const aspect = this.width / this.height;
     if (this.camera_type === "perspective") {
     if (this.camera_type === "perspective") {
-      this.camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
+      this.camera = new THREE.PerspectiveCamera(
+        this.camera_params.fov,
+        this.width / this.height,
+        this.camera_params.near,
+        this.camera_params.far
+      );
     } else {
     } else {
-      this.camera = new THREE.OrthographicCamera(-aspect, aspect, 1, -1, 0.1, 1000);
+      this.camera = new THREE.OrthographicCamera(
+        (-this.camera_params.size / 2) * (this.width / this.height),
+        (this.camera_params.size / 2) * (this.width / this.height),
+        this.camera_params.size / 2,
+        -this.camera_params.size / 2,
+        this.camera_params.near,
+        this.camera_params.far
+      );
     }
     }
+    this.look_at = new THREE.Vector3(0, 0, 0);
     this.camera.lookAt(this.look_at);
     this.camera.lookAt(this.look_at);
     this.camera.up = new THREE.Vector3(0, 0, 1);
     this.camera.up = new THREE.Vector3(0, 0, 1);
     this.camera.position.set(0, -3, 5);
     this.camera.position.set(0, -3, 5);
@@ -416,8 +427,8 @@ export default {
       this.text3d_renderer.setSize(clientWidth, clientHeight);
       this.text3d_renderer.setSize(clientWidth, clientHeight);
       this.camera.aspect = clientWidth / clientHeight;
       this.camera.aspect = clientWidth / clientHeight;
       if (this.camera_type === "orthographic") {
       if (this.camera_type === "orthographic") {
-        this.camera.left = -this.camera.aspect;
-        this.camera.right = this.camera.aspect;
+        this.camera.left = (-this.camera.aspect * this.camera_params.size) / 2;
+        this.camera.right = (this.camera.aspect * this.camera_params.size) / 2;
       }
       }
       this.camera.updateProjectionMatrix();
       this.camera.updateProjectionMatrix();
     },
     },
@@ -428,6 +439,7 @@ export default {
     height: Number,
     height: Number,
     grid: Boolean,
     grid: Boolean,
     camera_type: String,
     camera_type: String,
+    camera_params: Object,
     drag_constraints: String,
     drag_constraints: String,
   },
   },
 };
 };

+ 31 - 3
nicegui/elements/scene.py

@@ -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__()

+ 8 - 6
website/documentation/content/scene_documentation.py

@@ -113,12 +113,14 @@ async def wait_for_init() -> None:
         scene.move_camera(x=1, y=-1, z=1.5, duration=2)
         scene.move_camera(x=1, y=-1, z=1.5, duration=2)
 
 
 
 
-# @doc.demo('Orthographic Camera', '''
-#     You can use the `camera_type` argument to `ui.scene` to use an orthographic instead of a perspective camera.
-# ''')
-# def orthographic_camera() -> None:
-#     with ui.scene(camera_type='orthographic').classes('w-full h-64') as scene:
-#         scene.box()
+@doc.demo('Camera Parameters', '''
+    You can use the `camera` argument to `ui.scene` to use a custom camera.
+    This allows you to set the field of view of a perspective camera or the size of an orthographic camera.
+''')
+def orthographic_camera() -> None:
+    with ui.scene(camera=ui.scene.orthographic_camera(size=2)) \
+            .classes('w-full h-64') as scene:
+        scene.box()
 
 
 
 
 doc.reference(ui.scene)
 doc.reference(ui.scene)