scene_view.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import asyncio
  2. from typing import Any, Callable, Optional
  3. from typing_extensions import Self
  4. from ..awaitable_response import AwaitableResponse, NullResponse
  5. from ..element import Element
  6. from ..events import GenericEventArguments, SceneClickEventArguments, SceneClickHit, handle_event
  7. from .scene import Scene, SceneCamera
  8. class SceneView(Element,
  9. component='scene_view.js',
  10. libraries=['lib/tween/tween.umd.js'],
  11. exposed_libraries=['lib/three/three.module.js']):
  12. def __init__(self,
  13. scene: Scene,
  14. width: int = 400,
  15. height: int = 300,
  16. camera: Optional[SceneCamera] = None,
  17. on_click: Optional[Callable[..., Any]] = None,
  18. ) -> None:
  19. """Scene View
  20. Display an additional view of a 3D scene using `three.js <https://threejs.org/>`_.
  21. This component can only show a scene and not modify it.
  22. You can, however, independently move the camera.
  23. Current limitation: 2D and 3D text objects are not supported and will not be displayed in the scene view.
  24. :param scene: the scene which will be shown on the canvas
  25. :param width: width of the canvas
  26. :param height: height of the canvas
  27. :param camera: camera definition, either instance of ``ui.scene.perspective_camera`` (default) or ``ui.scene.orthographic_camera``
  28. :param on_click: callback to execute when a 3D object is clicked
  29. """
  30. super().__init__()
  31. self._props['width'] = width
  32. self._props['height'] = height
  33. self._props['scene_id'] = scene.id
  34. self.camera = camera or Scene.perspective_camera()
  35. self._props['camera_type'] = self.camera.type
  36. self._props['camera_params'] = self.camera.params
  37. self._click_handlers = [on_click] if on_click else []
  38. self.is_initialized = False
  39. self.on('init', self._handle_init)
  40. self.on('click3d', self._handle_click)
  41. def on_click(self, callback: Callable[..., Any]) -> Self:
  42. """Add a callback to be invoked when a 3D object is clicked."""
  43. self._click_handlers.append(callback)
  44. return self
  45. def _handle_init(self, e: GenericEventArguments) -> None:
  46. self.is_initialized = True
  47. with self.client.individual_target(e.args['socket_id']):
  48. self.move_camera(duration=0)
  49. async def initialized(self) -> None:
  50. """Wait until the scene is initialized."""
  51. event = asyncio.Event()
  52. self.on('init', event.set, [])
  53. await self.client.connected()
  54. await event.wait()
  55. def run_method(self, name: str, *args: Any, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse:
  56. if not self.is_initialized:
  57. return NullResponse()
  58. return super().run_method(name, *args, timeout=timeout, check_interval=check_interval)
  59. def _handle_click(self, e: GenericEventArguments) -> None:
  60. arguments = SceneClickEventArguments(
  61. sender=self,
  62. client=self.client,
  63. click_type=e.args['click_type'],
  64. button=e.args['button'],
  65. alt=e.args['alt_key'],
  66. ctrl=e.args['ctrl_key'],
  67. meta=e.args['meta_key'],
  68. shift=e.args['shift_key'],
  69. hits=[SceneClickHit(
  70. object_id=hit['object_id'],
  71. object_name=hit['object_name'],
  72. x=hit['point']['x'],
  73. y=hit['point']['y'],
  74. z=hit['point']['z'],
  75. ) for hit in e.args['hits']],
  76. )
  77. for handler in self._click_handlers:
  78. handle_event(handler, arguments)
  79. def move_camera(self,
  80. x: Optional[float] = None,
  81. y: Optional[float] = None,
  82. z: Optional[float] = None,
  83. look_at_x: Optional[float] = None,
  84. look_at_y: Optional[float] = None,
  85. look_at_z: Optional[float] = None,
  86. up_x: Optional[float] = None,
  87. up_y: Optional[float] = None,
  88. up_z: Optional[float] = None,
  89. duration: float = 0.5) -> None:
  90. """Move the camera to a new position.
  91. :param x: camera x position
  92. :param y: camera y position
  93. :param z: camera z position
  94. :param look_at_x: camera look-at x position
  95. :param look_at_y: camera look-at y position
  96. :param look_at_z: camera look-at z position
  97. :param up_x: x component of the camera up vector
  98. :param up_y: y component of the camera up vector
  99. :param up_z: z component of the camera up vector
  100. :param duration: duration of the movement in seconds (default: `0.5`)
  101. """
  102. self.camera.x = self.camera.x if x is None else x
  103. self.camera.y = self.camera.y if y is None else y
  104. self.camera.z = self.camera.z if z is None else z
  105. self.camera.look_at_x = self.camera.look_at_x if look_at_x is None else look_at_x
  106. self.camera.look_at_y = self.camera.look_at_y if look_at_y is None else look_at_y
  107. self.camera.look_at_z = self.camera.look_at_z if look_at_z is None else look_at_z
  108. self.camera.up_x = self.camera.up_x if up_x is None else up_x
  109. self.camera.up_y = self.camera.up_y if up_y is None else up_y
  110. self.camera.up_z = self.camera.up_z if up_z is None else up_z
  111. self.run_method('move_camera',
  112. self.camera.x, self.camera.y, self.camera.z,
  113. self.camera.look_at_x, self.camera.look_at_y, self.camera.look_at_z,
  114. self.camera.up_x, self.camera.up_y, self.camera.up_z, duration)