scene.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. from dataclasses import dataclass
  2. from pathlib import Path
  3. from typing import Any, Callable, Dict, List, Optional, Union
  4. from .. import binding, globals
  5. from ..dependencies import register_library, register_vue_component
  6. from ..element import Element
  7. from ..events import SceneClickEventArguments, SceneClickHit, handle_event
  8. from .scene_object3d import Object3D
  9. from .scene_objects import Scene as SceneObject
  10. register_vue_component(name='scene', path=Path(__file__).parent.joinpath('scene.js'))
  11. register_library(name='three',
  12. path=Path(__file__).parent.joinpath('lib', 'three', 'three.module.js'), expose=True)
  13. register_library(name='CSS2DRenderer',
  14. path=Path(__file__).parent.joinpath('lib', 'three', 'modules', 'CSS2DRenderer.js'), expose=True)
  15. register_library(name='CSS3DRenderer',
  16. path=Path(__file__).parent.joinpath('lib', 'three', 'modules', 'CSS3DRenderer.js'), expose=True)
  17. register_library(name='OrbitControls',
  18. path=Path(__file__).parent.joinpath('lib', 'three', 'modules', 'OrbitControls.js'), expose=True)
  19. register_library(name='STLLoader',
  20. path=Path(__file__).parent.joinpath('lib', 'three', 'modules', 'STLLoader.js'), expose=True)
  21. register_library(name='tween',
  22. path=Path(__file__).parent.joinpath('lib', 'tween', 'tween.umd.js'))
  23. @dataclass
  24. class SceneCamera:
  25. x: float = 0
  26. y: float = -3
  27. z: float = 5
  28. look_at_x: float = 0
  29. look_at_y: float = 0
  30. look_at_z: float = 0
  31. up_x: float = 0
  32. up_y: float = 0
  33. up_z: float = 1
  34. @dataclass
  35. class SceneObject:
  36. id: str = 'scene'
  37. class Scene(Element):
  38. from .scene_objects import Box as box
  39. from .scene_objects import Curve as curve
  40. from .scene_objects import Cylinder as cylinder
  41. from .scene_objects import Extrusion as extrusion
  42. from .scene_objects import Group as group
  43. from .scene_objects import Line as line
  44. from .scene_objects import PointCloud as point_cloud
  45. from .scene_objects import QuadraticBezierTube as quadratic_bezier_tube
  46. from .scene_objects import Ring as ring
  47. from .scene_objects import Sphere as sphere
  48. from .scene_objects import SpotLight as spot_light
  49. from .scene_objects import Stl as stl
  50. from .scene_objects import Text as text
  51. from .scene_objects import Text3d as text3d
  52. from .scene_objects import Texture as texture
  53. def __init__(self,
  54. width: int = 400,
  55. height: int = 300,
  56. grid: bool = True,
  57. on_click: Optional[Callable] = None) -> None:
  58. """3D Scene
  59. Display a 3d scene using `three.js <https://threejs.org/>`_.
  60. Currently NiceGUI supports boxes, spheres, cylinders/cones, extrusions, straight lines, curves and textured meshes.
  61. Objects can be translated, rotated and displayed with different color, opacity or as wireframes.
  62. They can also be grouped to apply joint movements.
  63. :param width: width of the canvas
  64. :param height: height of the canvas
  65. :param grid: whether to display a grid
  66. :param on_click: callback to execute when a 3d object is clicked
  67. """
  68. super().__init__('scene')
  69. self._props['width'] = width
  70. self._props['height'] = height
  71. self._props['grid'] = grid
  72. self._props['key'] = self.id # HACK: workaround for #600
  73. self.objects: Dict[str, Object3D] = {}
  74. self.stack: List[Union[Object3D, SceneObject]] = [SceneObject()]
  75. self.camera: SceneCamera = SceneCamera()
  76. self.on_click = on_click
  77. self.is_initialized = False
  78. self.on('init', self.handle_init)
  79. self.on('click3d', self.handle_click)
  80. self.use_component('scene')
  81. self.use_library('three')
  82. self.use_library('CSS2DRenderer')
  83. self.use_library('CSS3DRenderer')
  84. self.use_library('OrbitControls')
  85. self.use_library('STLLoader')
  86. self.use_library('tween')
  87. def handle_init(self, msg: Dict) -> None:
  88. self.is_initialized = True
  89. with globals.socket_id(msg['args']):
  90. self.move_camera(duration=0)
  91. for object in self.objects.values():
  92. object.send()
  93. def run_method(self, name: str, *args: Any) -> None:
  94. if not self.is_initialized:
  95. return
  96. super().run_method(name, *args)
  97. def handle_click(self, msg: Dict) -> None:
  98. arguments = SceneClickEventArguments(
  99. sender=self,
  100. client=self.client,
  101. click_type=msg['args']['click_type'],
  102. button=msg['args']['button'],
  103. alt=msg['args']['alt_key'],
  104. ctrl=msg['args']['ctrl_key'],
  105. meta=msg['args']['meta_key'],
  106. shift=msg['args']['shift_key'],
  107. hits=[SceneClickHit(
  108. object_id=hit['object_id'],
  109. object_name=hit['object_name'],
  110. x=hit['point']['x'],
  111. y=hit['point']['y'],
  112. z=hit['point']['z'],
  113. ) for hit in msg['args']['hits']],
  114. )
  115. handle_event(self.on_click, arguments)
  116. def __len__(self) -> int:
  117. return len(self.objects)
  118. def move_camera(self,
  119. x: Optional[float] = None,
  120. y: Optional[float] = None,
  121. z: Optional[float] = None,
  122. look_at_x: Optional[float] = None,
  123. look_at_y: Optional[float] = None,
  124. look_at_z: Optional[float] = None,
  125. up_x: Optional[float] = None,
  126. up_y: Optional[float] = None,
  127. up_z: Optional[float] = None,
  128. duration: float = 0.5) -> None:
  129. self.camera.x = self.camera.x if x is None else x
  130. self.camera.y = self.camera.y if y is None else y
  131. self.camera.z = self.camera.z if z is None else z
  132. self.camera.look_at_x = self.camera.look_at_x if look_at_x is None else look_at_x
  133. self.camera.look_at_y = self.camera.look_at_y if look_at_y is None else look_at_y
  134. self.camera.look_at_z = self.camera.look_at_z if look_at_z is None else look_at_z
  135. self.camera.up_x = self.camera.up_x if up_x is None else up_x
  136. self.camera.up_y = self.camera.up_y if up_y is None else up_y
  137. self.camera.up_z = self.camera.up_z if up_z is None else up_z
  138. self.run_method('move_camera',
  139. self.camera.x, self.camera.y, self.camera.z,
  140. self.camera.look_at_x, self.camera.look_at_y, self.camera.look_at_z,
  141. self.camera.up_x, self.camera.up_y, self.camera.up_z, duration)
  142. def delete(self) -> None:
  143. binding.remove(list(self.objects.values()), Object3D)
  144. super().delete()