123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- from dataclasses import dataclass
- from typing import Any, Callable, Dict, List, Optional, Union
- from typing_extensions import Self
- from .. import binding, globals # pylint: disable=redefined-builtin
- from ..dataclasses import KWONLY_SLOTS
- from ..element import Element
- from ..events import (GenericEventArguments, SceneClickEventArguments, SceneClickHit, SceneDragEventArguments,
- handle_event)
- from .scene_object3d import Object3D
- @dataclass(**KWONLY_SLOTS)
- class SceneCamera:
- x: float = 0
- y: float = -3
- z: float = 5
- look_at_x: float = 0
- look_at_y: float = 0
- look_at_z: float = 0
- up_x: float = 0
- up_y: float = 0
- up_z: float = 1
- @dataclass(**KWONLY_SLOTS)
- class SceneObject:
- id: str = 'scene'
- class Scene(Element,
- component='scene.js',
- libraries=['lib/tween/tween.umd.js'],
- exposed_libraries=[
- 'lib/three/three.module.js',
- 'lib/three/modules/CSS2DRenderer.js',
- 'lib/three/modules/CSS3DRenderer.js',
- 'lib/three/modules/DragControls.js',
- 'lib/three/modules/OrbitControls.js',
- 'lib/three/modules/STLLoader.js',
- ]):
- # pylint: disable=import-outside-toplevel
- from .scene_objects import Box as box
- from .scene_objects import Curve as curve
- from .scene_objects import Cylinder as cylinder
- from .scene_objects import Extrusion as extrusion
- from .scene_objects import Group as group
- from .scene_objects import Line as line
- from .scene_objects import PointCloud as point_cloud
- from .scene_objects import QuadraticBezierTube as quadratic_bezier_tube
- from .scene_objects import Ring as ring
- from .scene_objects import Sphere as sphere
- from .scene_objects import SpotLight as spot_light
- from .scene_objects import Stl as stl
- from .scene_objects import Text as text
- from .scene_objects import Text3d as text3d
- from .scene_objects import Texture as texture
- def __init__(self,
- width: int = 400,
- height: int = 300,
- grid: bool = True,
- on_click: Optional[Callable[..., Any]] = None,
- on_drag_start: Optional[Callable[..., Any]] = None,
- on_drag_end: Optional[Callable[..., Any]] = None,
- drag_constraints: str = '',
- ) -> None:
- """3D Scene
- Display a 3D scene using `three.js <https://threejs.org/>`_.
- Currently NiceGUI supports boxes, spheres, cylinders/cones, extrusions, straight lines, curves and textured meshes.
- Objects can be translated, rotated and displayed with different color, opacity or as wireframes.
- They can also be grouped to apply joint movements.
- :param width: width of the canvas
- :param height: height of the canvas
- :param grid: whether to display a grid
- :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_end: callback to execute when a 3D object is dropped
- :param drag_constraints: comma-separated JavaScript expression for constraining positions of dragged objects (e.g. ``'x = 0, z = y / 2'``)
- """
- super().__init__()
- self._props['width'] = width
- self._props['height'] = height
- self._props['grid'] = grid
- self.objects: Dict[str, Object3D] = {}
- self.stack: List[Union[Object3D, SceneObject]] = [SceneObject()]
- self.camera: SceneCamera = SceneCamera()
- self._click_handler = on_click
- self._drag_start_handler = on_drag_start
- self._drag_end_handler = on_drag_end
- self.is_initialized = False
- self.on('init', self._handle_init)
- self.on('click3d', self._handle_click)
- self.on('dragstart', self._handle_drag)
- self.on('dragend', self._handle_drag)
- self._props['drag_constraints'] = drag_constraints
- def __enter__(self) -> Self:
- Object3D.current_scene = self
- super().__enter__()
- return self
- def __getattribute__(self, name: str) -> Any:
- attribute = super().__getattribute__(name)
- if isinstance(attribute, type) and issubclass(attribute, Object3D):
- Object3D.current_scene = self
- return attribute
- def _handle_init(self, e: GenericEventArguments) -> None:
- self.is_initialized = True
- with globals.socket_id(e.args['socket_id']):
- self.move_camera(duration=0)
- for obj in self.objects.values():
- obj.send()
- def run_method(self, name: str, *args: Any) -> None:
- """Run a method on the client.
- :param name: name of the method
- :param args: arguments to pass to the method
- """
- if not self.is_initialized:
- return
- super().run_method(name, *args)
- def _handle_click(self, e: GenericEventArguments) -> None:
- arguments = SceneClickEventArguments(
- sender=self,
- client=self.client,
- click_type=e.args['click_type'],
- button=e.args['button'],
- alt=e.args['alt_key'],
- ctrl=e.args['ctrl_key'],
- meta=e.args['meta_key'],
- shift=e.args['shift_key'],
- hits=[SceneClickHit(
- object_id=hit['object_id'],
- object_name=hit['object_name'],
- x=hit['point']['x'],
- y=hit['point']['y'],
- z=hit['point']['z'],
- ) for hit in e.args['hits']],
- )
- handle_event(self._click_handler, arguments)
- def _handle_drag(self, e: GenericEventArguments) -> None:
- arguments = SceneDragEventArguments(
- sender=self,
- client=self.client,
- type=e.args['type'],
- object_id=e.args['object_id'],
- object_name=e.args['object_name'],
- x=e.args['x'],
- y=e.args['y'],
- z=e.args['z'],
- )
- if arguments.type == 'dragend':
- self.objects[arguments.object_id].move(arguments.x, arguments.y, arguments.z)
- handle_event(self._drag_start_handler if arguments.type == 'dragstart' else self._drag_end_handler, arguments)
- def __len__(self) -> int:
- return len(self.objects)
- def move_camera(self,
- x: Optional[float] = None,
- y: Optional[float] = None,
- z: Optional[float] = None,
- look_at_x: Optional[float] = None,
- look_at_y: Optional[float] = None,
- look_at_z: Optional[float] = None,
- up_x: Optional[float] = None,
- up_y: Optional[float] = None,
- up_z: Optional[float] = None,
- duration: float = 0.5) -> None:
- """Move the camera to a new position.
- :param x: camera x position
- :param y: camera y position
- :param z: camera z position
- :param look_at_x: camera look-at x position
- :param look_at_y: camera look-at y position
- :param look_at_z: camera look-at z position
- :param up_x: x component of the camera up vector
- :param up_y: y component of the camera up vector
- :param up_z: z component of the camera up vector
- :param duration: duration of the movement in seconds (default: `0.5`)
- """
- self.camera.x = self.camera.x if x is None else x
- self.camera.y = self.camera.y if y is None else y
- self.camera.z = self.camera.z if z is None else z
- self.camera.look_at_x = self.camera.look_at_x if look_at_x is None else look_at_x
- self.camera.look_at_y = self.camera.look_at_y if look_at_y is None else look_at_y
- self.camera.look_at_z = self.camera.look_at_z if look_at_z is None else look_at_z
- self.camera.up_x = self.camera.up_x if up_x is None else up_x
- self.camera.up_y = self.camera.up_y if up_y is None else up_y
- self.camera.up_z = self.camera.up_z if up_z is None else up_z
- self.run_method('move_camera',
- self.camera.x, self.camera.y, self.camera.z,
- self.camera.look_at_x, self.camera.look_at_y, self.camera.look_at_z,
- self.camera.up_x, self.camera.up_y, self.camera.up_z, duration)
- def _handle_delete(self) -> None:
- binding.remove(list(self.objects.values()), Object3D)
- super()._handle_delete()
- def delete_objects(self, predicate: Callable[[Object3D], bool] = lambda _: True) -> None:
- """Remove objects from the scene.
- :param predicate: function which returns `True` for objects which should be deleted
- """
- for obj in list(self.objects.values()):
- if predicate(obj):
- obj.delete()
- def clear(self) -> None:
- """Remove all objects from the scene."""
- super().clear()
- self.delete_objects()
|