scene_object3d.py 3.7 KB

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