scene_object3d.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from __future__ import annotations
  2. import asyncio
  3. from typing import List, Optional
  4. import uuid
  5. import numpy as np
  6. from justpy.htmlcomponents import WebPage
  7. from ..task_logger import create_task
  8. class Object3D:
  9. stack: List[Object3D] = []
  10. def __init__(self, type: str, *args):
  11. self.type = type
  12. self.id = str(uuid.uuid4())
  13. self.name = None
  14. self.parent = self.stack[-1]
  15. self.view = self.parent.view
  16. self.page = self.parent.page
  17. self.args = args
  18. self.color = '#ffffff'
  19. self.opacity = 1.0
  20. self.x = 0
  21. self.y = 0
  22. self.z = 0
  23. self.R = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
  24. self.sx = 1
  25. self.sy = 1
  26. self.sz = 1
  27. self.run_command(self._create_command)
  28. self.view.objects[self.id] = self
  29. def with_name(self, name: str) -> Object3D:
  30. self.name = name
  31. return self
  32. def run_command(self, command: str, socket=None):
  33. sockets = [socket] if socket else WebPage.sockets.get(self.page.page_id, {}).values()
  34. for socket in sockets:
  35. create_task(self.view.run_method(command, socket), name=command)
  36. def send_to(self, socket):
  37. self.run_command(self._create_command, socket)
  38. self.run_command(self._material_command, socket)
  39. self.run_command(self._move_command, socket)
  40. self.run_command(self._rotate_command, socket)
  41. self.run_command(self._scale_command, socket)
  42. def __enter__(self):
  43. self.stack.append(self)
  44. return self
  45. def __exit__(self, *_):
  46. self.stack.pop()
  47. @property
  48. def _create_command(self):
  49. return f'create("{self.type}", "{self.id}", "{self.parent.id}", {str(self.args)[1:-1]})'
  50. @property
  51. def _material_command(self):
  52. return f'material("{self.id}", "{self.color}", "{self.opacity}")'
  53. @property
  54. def _move_command(self):
  55. return f'move("{self.id}", {self.x}, {self.y}, {self.z})'
  56. @property
  57. def _rotate_command(self):
  58. return f'rotate("{self.id}", {self.R})'
  59. @property
  60. def _scale_command(self):
  61. return f'scale("{self.id}", {self.sx}, {self.sy}, {self.sz})'
  62. @property
  63. def _delete_command(self):
  64. return f'delete("{self.id}")'
  65. def material(self, color: str = '#ffffff', opacity: float = 1.0):
  66. if self.color != color or self.opacity != opacity:
  67. self.color = color
  68. self.opacity = opacity
  69. self.run_command(self._material_command)
  70. return self
  71. def move(self, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> Object3D:
  72. if self.x != x or self.y != y or self.z != z:
  73. self.x = x
  74. self.y = y
  75. self.z = z
  76. self.run_command(self._move_command)
  77. return self
  78. def rotate(self, omega: float, phi: float, kappa: float):
  79. Rx = np.array([[1, 0, 0], [0, np.cos(omega), -np.sin(omega)], [0, np.sin(omega), np.cos(omega)]])
  80. Ry = np.array([[np.cos(phi), 0, np.sin(phi)], [0, 1, 0], [-np.sin(phi), 0, np.cos(phi)]])
  81. Rz = np.array([[np.cos(kappa), -np.sin(kappa), 0], [np.sin(kappa), np.cos(kappa), 0], [0, 0, 1]])
  82. return self.rotate_R((Rz @ Ry @ Rx).tolist())
  83. def rotate_R(self, R: List[List[float]]):
  84. if self.R != R:
  85. self.R = R
  86. self.run_command(self._rotate_command)
  87. return self
  88. def scale(self, sx: float = 1.0, sy: Optional[float] = None, sz: Optional[float] = None):
  89. if sy is None:
  90. sy = sx
  91. if sz is None:
  92. sz = sx
  93. if self.sx != sx or self.sy != sy or self.sz != sz:
  94. self.sx = sx
  95. self.sy = sy
  96. self.sz = sz
  97. self.run_command(self._scale_command)
  98. return self
  99. def delete(self):
  100. del self.view.objects[self.id]
  101. self.run_command(self._delete_command)