ソースを参照

starting to update ui.scene

Falko Schindler 2 年 前
コミット
4248acd775

+ 2 - 0
nicegui/element.py

@@ -147,5 +147,7 @@ class Element(ABC, Visibility):
         create_task(globals.sio.emit('update', {'elements': elements}, room=str(self.client.id)))
 
     def run_method(self, name: str, *args: Any) -> None:
+        if globals.loop is None:
+            return
         data = {'id': self.id, 'name': name, 'args': args}
         create_task(globals.sio.emit('run_method', data, room=str(self.client.id)))

+ 0 - 153
nicegui/elements/old/scene.py

@@ -1,153 +0,0 @@
-import traceback
-from dataclasses import dataclass
-from typing import Callable, Optional
-
-import websockets
-from justpy import WebPage
-
-from ..auto_context import get_view_stack
-from ..events import handle_event
-from ..page import Page
-from ..routes import add_dependencies
-from ..task_logger import create_task
-from .custom_view import CustomView
-from .element import Element
-from .scene_object3d import Object3D
-
-add_dependencies(__file__, [
-    'three.min.js',
-    'CSS2DRenderer.js',
-    'CSS3DRenderer.js',
-    'OrbitControls.js',
-    'STLLoader.js',
-    'tween.umd.min.js',
-])
-
-
-@dataclass
-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
-
-    def create_move_command(self, duration: float = 0) -> str:
-        return 'move_camera(' \
-            f'{self.x}, {self.y}, {self.z}, ' \
-            f'{self.look_at_x}, {self.look_at_y}, {self.look_at_z}, ' \
-            f'{self.up_x}, {self.up_y}, {self.up_z}, {duration})'
-
-
-class SceneView(CustomView):
-
-    def __init__(self, *, width: int, height: int, on_click: Optional[Callable]):
-        super().__init__('scene', width=width, height=height)
-        self.on_click = on_click
-        self.allowed_events = ['onConnect', 'onClick']
-        self.initialize(temp=False, onConnect=self.handle_connect, onClick=self.handle_click)
-        self.objects = {}
-        self.camera: SceneCamera = SceneCamera()
-
-    def handle_connect(self, msg):
-        try:
-            for object in self.objects.values():
-                object.send_to(msg.websocket)
-            create_task(self.run_method(self.camera.create_move_command(), msg.websocket), name='move camera (connect)')
-        except:
-            traceback.print_exc()
-
-    def handle_click(self, msg) -> Optional[bool]:
-        try:
-            for hit in msg.hits:
-                hit.object = self.objects.get(hit.object_id)
-            return handle_event(self.on_click, msg)
-        except:
-            traceback.print_exc()
-
-    async def run_method(self, command, websocket):
-        try:
-            await websocket.send_json({'type': 'run_method', 'data': command, 'id': self.id})
-        except (websockets.exceptions.ConnectionClosedOK, RuntimeError):
-            pass
-        return True
-
-    def __len__(self):
-        return len(self.objects)
-
-
-class Scene(Element):
-    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 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, on_click: Optional[Callable] = 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 on_click: callback to execute when a 3d object is clicked
-        """
-        super().__init__(SceneView(width=width, height=height, on_click=on_click))
-
-    def __enter__(self):
-        get_view_stack().append(self.view)
-        scene = self.view.objects.get('scene', SceneObject(self.view, self.page))
-        Object3D.stack.clear()
-        Object3D.stack.append(scene)
-        return self
-
-    def __exit__(self, *_):
-        get_view_stack().pop()
-
-    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):
-        camera: SceneCamera = self.view.camera
-        camera.x = camera.x if x is None else x
-        camera.y = camera.y if y is None else y
-        camera.z = camera.z if z is None else z
-        camera.look_at_x = camera.look_at_x if look_at_x is None else look_at_x
-        camera.look_at_y = camera.look_at_y if look_at_y is None else look_at_y
-        camera.look_at_z = camera.look_at_z if look_at_z is None else look_at_z
-        camera.up_x = camera.up_x if up_x is None else up_x
-        camera.up_y = camera.up_y if up_y is None else up_y
-        camera.up_z = camera.up_z if up_z is None else up_z
-        for socket in WebPage.sockets.get(self.page.page_id, {}).values():
-            create_task(self.view.run_method(camera.create_move_command(duration), socket), name='move camera')
-
-
-class SceneObject:
-
-    def __init__(self, view: SceneView, page: Page):
-        self.id = 'scene'
-        self.view = view
-        self.page = page

+ 0 - 137
nicegui/elements/old/scene_object3d.py

@@ -1,137 +0,0 @@
-from __future__ import annotations
-
-import uuid
-from typing import List, Optional
-
-import numpy as np
-from justpy.htmlcomponents import WebPage
-
-from ..task_logger import create_task
-
-
-class Object3D:
-    stack: List[Object3D] = []
-
-    def __init__(self, type: str, *args):
-        self.type = type
-        self.id = str(uuid.uuid4())
-        self.name = None
-        self.parent = self.stack[-1]
-        self.view = self.parent.view
-        self.page = self.parent.page
-        self.args = list(args)
-        self.color = '#ffffff'
-        self.opacity = 1.0
-        self.side_: str = 'front'
-        self.visible_: bool = True
-        self.x = 0
-        self.y = 0
-        self.z = 0
-        self.R = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
-        self.sx = 1
-        self.sy = 1
-        self.sz = 1
-        self.run_command(self._create_command)
-        self.view.objects[self.id] = self
-
-    def with_name(self, name: str) -> Object3D:
-        self.name = name
-        return self
-
-    def run_command(self, command: str, socket=None):
-        sockets = [socket] if socket else WebPage.sockets.get(self.page.page_id, {}).values()
-        for socket in sockets:
-            create_task(self.view.run_method(command, socket), name=command)
-
-    def send_to(self, socket):
-        self.run_command(self._create_command, socket)
-        self.run_command(self._material_command, socket)
-        self.run_command(self._move_command, socket)
-        self.run_command(self._rotate_command, socket)
-        self.run_command(self._scale_command, socket)
-        self.run_command(self._visible_command, socket)
-
-    def __enter__(self):
-        self.stack.append(self)
-        return self
-
-    def __exit__(self, *_):
-        self.stack.pop()
-
-    @property
-    def _create_command(self):
-        return f'create("{self.type}", "{self.id}", "{self.parent.id}", {str(self.args)[1:-1]})'
-
-    @property
-    def _material_command(self):
-        return f'material("{self.id}", "{self.color}", "{self.opacity}", "{self.side_}")'
-
-    @property
-    def _move_command(self):
-        return f'move("{self.id}", {self.x}, {self.y}, {self.z})'
-
-    @property
-    def _rotate_command(self):
-        return f'rotate("{self.id}", {self.R})'
-
-    @property
-    def _scale_command(self):
-        return f'scale("{self.id}", {self.sx}, {self.sy}, {self.sz})'
-
-    @property
-    def _visible_command(self):
-        return f'visible("{self.id}", {self.visible_})'
-
-    @property
-    def _delete_command(self):
-        return f'delete("{self.id}")'
-
-    def material(self, color: str = '#ffffff', opacity: float = 1.0, side: str = 'front'):
-        if self.color != color or self.opacity != opacity or self.side_ != side:
-            self.color = color
-            self.opacity = opacity
-            self.side_ = side
-            self.run_command(self._material_command)
-        return self
-
-    def move(self, x: float = 0.0, y: float = 0.0, z: float = 0.0) -> Object3D:
-        if self.x != x or self.y != y or self.z != z:
-            self.x = x
-            self.y = y
-            self.z = z
-            self.run_command(self._move_command)
-        return self
-
-    def rotate(self, omega: float, phi: float, kappa: float):
-        Rx = np.array([[1, 0, 0], [0, np.cos(omega), -np.sin(omega)], [0, np.sin(omega), np.cos(omega)]])
-        Ry = np.array([[np.cos(phi), 0, np.sin(phi)], [0, 1, 0], [-np.sin(phi), 0, np.cos(phi)]])
-        Rz = np.array([[np.cos(kappa), -np.sin(kappa), 0], [np.sin(kappa), np.cos(kappa), 0], [0, 0, 1]])
-        return self.rotate_R((Rz @ Ry @ Rx).tolist())
-
-    def rotate_R(self, R: List[List[float]]):
-        if self.R != R:
-            self.R = R
-            self.run_command(self._rotate_command)
-        return self
-
-    def scale(self, sx: float = 1.0, sy: Optional[float] = None, sz: Optional[float] = None):
-        if sy is None:
-            sy = sx
-        if sz is None:
-            sz = sx
-        if self.sx != sx or self.sy != sy or self.sz != sz:
-            self.sx = sx
-            self.sy = sy
-            self.sz = sz
-            self.run_command(self._scale_command)
-        return self
-
-    def visible(self, value: bool = True):
-        if self.visible_ != value:
-            self.visible_ = value
-            self.run_command(self._visible_command)
-        return self
-
-    def delete(self):
-        del self.view.objects[self.id]
-        self.run_command(self._delete_command)

+ 16 - 42
nicegui/elements/old/scene.js → nicegui/elements/scene.js

@@ -54,9 +54,9 @@ function texture_material(texture) {
   });
 }
 
-Vue.component("scene", {
+export default {
   template: `
-    <div v-bind:id="jp_props.id" style="position:relative">
+    <div style="position:relative">
       <canvas style="position:relative"></canvas>
       <div style="position:absolute;pointer-events:none;top:0"></div>
       <div style="position:absolute;pointer-events:none;top:0"></div>
@@ -66,11 +66,8 @@ Vue.component("scene", {
     scene = new THREE.Scene();
     objects.set("scene", scene);
 
-    const width = this.$props.jp_props.options.width;
-    const height = this.$props.jp_props.options.height;
-
     look_at = new THREE.Vector3(0, 0, 0);
-    camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
+    camera = new THREE.PerspectiveCamera(75, this.width / this.height, 0.1, 1000);
     camera.lookAt(look_at);
     camera.up = new THREE.Vector3(0, 0, 1);
     camera.position.set(0, -3, 5);
@@ -83,20 +80,20 @@ Vue.component("scene", {
     const renderer = new THREE.WebGLRenderer({
       antialias: true,
       alpha: true,
-      canvas: document.getElementById(this.$props.jp_props.id).children[0],
+      canvas: this.$el.children[0],
     });
     renderer.setClearColor("#eee");
-    renderer.setSize(width, height);
+    renderer.setSize(this.width, this.height);
 
     const text_renderer = new THREE.CSS2DRenderer({
-      element: document.getElementById(this.$props.jp_props.id).children[1],
+      element: this.$el.children[1],
     });
-    text_renderer.setSize(width, height);
+    text_renderer.setSize(this.width, this.height);
 
     const text3d_renderer = new THREE.CSS3DRenderer({
-      element: document.getElementById(this.$props.jp_props.id).children[2],
+      element: this.$el.children[2],
     });
-    text3d_renderer.setSize(width, height);
+    text3d_renderer.setSize(this.width, this.height);
 
     const ground = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: "#eee" }));
     ground.translateZ(-0.01);
@@ -125,12 +122,7 @@ Vue.component("scene", {
       let x = (mouseEvent.offsetX / renderer.domElement.width) * 2 - 1;
       let y = -(mouseEvent.offsetY / renderer.domElement.height) * 2 + 1;
       raycaster.setFromCamera({ x: x, y: y }, camera);
-      const event = {
-        event_type: "onClick",
-        vue_type: this.$props.jp_props.vue_type,
-        id: this.$props.jp_props.id,
-        page_id: page_id,
-        websocket_id: websocket_id,
+      this.$emit("click", {
         hits: raycaster
           .intersectObjects(scene.children, true)
           .filter((o) => o.object.object_id)
@@ -140,31 +132,12 @@ Vue.component("scene", {
           })),
         click_type: mouseEvent.type,
         shift_key: mouseEvent.shiftKey,
-      };
-      send_to_server(event, "event");
+      });
     };
-    document.getElementById(this.$props.jp_props.id).onclick = click_handler;
-    document.getElementById(this.$props.jp_props.id).ondblclick = click_handler;
-
-    comp_dict[this.$props.jp_props.id] = this;
-
-    const sendConnectEvent = () => {
-      if (websocket_id === "") return;
-      const event = {
-        event_type: "onConnect",
-        vue_type: this.$props.jp_props.vue_type,
-        id: this.$props.jp_props.id,
-        page_id: page_id,
-        websocket_id: websocket_id,
-      };
-      send_to_server(event, "event");
-      clearInterval(connectInterval);
-    };
-    const connectInterval = setInterval(sendConnectEvent, 100);
+    this.$el.onclick = click_handler;
+    this.$el.ondblclick = click_handler;
   },
 
-  updated() {},
-
   methods: {
     create(type, id, parent_id, ...args) {
       let mesh;
@@ -338,6 +311,7 @@ Vue.component("scene", {
   },
 
   props: {
-    jp_props: Object,
+    width: Number,
+    height: Number,
   },
-});
+};

+ 99 - 0
nicegui/elements/scene.py

@@ -0,0 +1,99 @@
+from dataclasses import dataclass
+from typing import Callable, Dict, List, Optional
+
+from ..element import Element
+from ..vue import register_component
+from .scene_object3d import Object3D
+from .scene_objects import Scene as SceneObject
+
+register_component('scene', __file__, 'scene.js', [
+    'lib/three.min.js',
+    'lib/CSS2DRenderer.js',
+    'lib/CSS3DRenderer.js',
+    'lib/OrbitControls.js',
+    'lib/STLLoader.js',
+    'lib/tween.umd.min.js',
+])
+
+
+@dataclass
+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
+
+
+class Scene(Element):
+    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 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, on_click: Optional[Callable] = None) -> 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 on_click: callback to execute when a 3d object is clicked
+        """
+        super().__init__('scene')
+        self._props['width'] = width
+        self._props['height'] = height
+        self.objects: Dict[Object3D] = {}
+        self.stack: List[Object3D] = []
+        with self:
+            SceneObject()
+        self.camera: SceneCamera = SceneCamera()
+        self.on('connect', self.handle_connect)
+
+    def handle_connect(self, msg: Dict) -> None:
+        print('CONNECT', msg, flush=True)
+
+    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:
+        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)

+ 126 - 0
nicegui/elements/scene_object3d.py

@@ -0,0 +1,126 @@
+from __future__ import annotations
+
+import uuid
+from typing import TYPE_CHECKING, Any, List, Optional
+
+import numpy as np
+
+from .. import globals
+
+if TYPE_CHECKING:
+    from .scene import Scene
+
+
+class Object3D:
+
+    def __init__(self, type: str, *args: Any) -> None:
+        self.type = type
+        self.id = str(uuid.uuid4())
+        self.name: Optional[str] = None
+        self.scene: 'Scene' = globals.client_stack[-1].slot_stack[-1].parent
+        self.scene.objects[self.id] = self
+        self.parent: Optional[Object3D] = self.scene.stack[-1] if self.scene.stack else None
+        self.args: List = list(args)
+        self.color: str = '#ffffff'
+        self.opacity: float = 1.0
+        self.side_: str = 'front'
+        self.visible_: bool = True
+        self.x: float = 0
+        self.y: float = 0
+        self.z: float = 0
+        self.R: List[List[float]] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
+        self.sx: float = 1
+        self.sy: float = 1
+        self.sz: float = 1
+        if self.parent:
+            self._create()
+
+    def with_name(self, name: str):
+        self.name = name
+        return self
+
+    def send(self) -> None:
+        self._create()
+        self._material()
+        self._move()
+        self._rotate()
+        self._scale()
+        self._visible()
+
+    def __enter__(self):
+        self.scene.stack.append(self)
+        return self
+
+    def __exit__(self, *_):
+        self.scene.stack.pop()
+
+    def _create(self) -> None:
+        self.scene.run_method('create', self.type, self.id, self.parent.id, self.args)
+
+    def _material(self) -> None:
+        self.scene.run_method('material', self.id, self.color, self.opacity, self.side_)
+
+    def _move(self) -> None:
+        self.scene.run_method('move', self.id, self.x, self.y, self.z)
+
+    def _rotate(self) -> None:
+        self.scene.run_method('rotate', self.id, self.R)
+
+    def _scale(self) -> None:
+        self.scene.run_method('scale', self.id, self.sx, self.sy, self.sz)
+
+    def _visible(self) -> None:
+        self.scene.run_method('visible', self.id, self.visible_)
+
+    def _delete(self) -> None:
+        self.scene.run_method('delete', self.id)
+
+    def material(self, color: str = '#ffffff', opacity: float = 1.0, side: str = 'front'):
+        if self.color != color or self.opacity != opacity or self.side_ != side:
+            self.color = color
+            self.opacity = opacity
+            self.side_ = side
+            self._material()
+        return self
+
+    def move(self, x: float = 0.0, y: float = 0.0, z: float = 0.0):
+        if self.x != x or self.y != y or self.z != z:
+            self.x = x
+            self.y = y
+            self.z = z
+            self._move()
+        return self
+
+    def rotate(self, omega: float, phi: float, kappa: float):
+        Rx = np.array([[1, 0, 0], [0, np.cos(omega), -np.sin(omega)], [0, np.sin(omega), np.cos(omega)]])
+        Ry = np.array([[np.cos(phi), 0, np.sin(phi)], [0, 1, 0], [-np.sin(phi), 0, np.cos(phi)]])
+        Rz = np.array([[np.cos(kappa), -np.sin(kappa), 0], [np.sin(kappa), np.cos(kappa), 0], [0, 0, 1]])
+        return self.rotate_R((Rz @ Ry @ Rx).tolist())
+
+    def rotate_R(self, R: List[List[float]]):
+        if self.R != R:
+            self.R = R
+            self._rotate()
+        return self
+
+    def scale(self, sx: float = 1.0, sy: Optional[float] = None, sz: Optional[float] = None):
+        if sy is None:
+            sy = sx
+        if sz is None:
+            sz = sx
+        if self.sx != sx or self.sy != sy or self.sz != sz:
+            self.sx = sx
+            self.sy = sy
+            self.sz = sz
+            self._scale()
+        return self
+
+    def visible(self, value: bool = True):
+        if self.visible_ != value:
+            self.visible_ = value
+            self._visible()
+        return self
+
+    def delete(self) -> None:
+        del self.scene.objects[self.id]
+        self._delete()

+ 20 - 23
nicegui/elements/old/scene_objects.py → nicegui/elements/scene_objects.py

@@ -3,20 +3,19 @@ from __future__ import annotations
 from typing import List, Optional
 
 import numpy as np
-from justpy import WebPage
 
 from .scene_object3d import Object3D
 
 
 class Scene(Object3D):
 
-    def __init__(self, view):
-        super().__init__('scene', view)
+    def __init__(self) -> None:
+        super().__init__('scene')
 
 
 class Group(Object3D):
 
-    def __init__(self):
+    def __init__(self) -> None:
         super().__init__('group')
 
 
@@ -27,7 +26,7 @@ class Box(Object3D):
                  height: float = 1.0,
                  depth: float = 1.0,
                  wireframe: bool = False,
-                 ):
+                 ) -> None:
         super().__init__('box', width, height, depth, wireframe)
 
 
@@ -38,7 +37,7 @@ class Sphere(Object3D):
                  width_segments: int = 32,
                  height_segments: int = 16,
                  wireframe: bool = False,
-                 ):
+                 ) -> None:
         super().__init__('sphere', radius, width_segments, height_segments, wireframe)
 
 
@@ -51,7 +50,7 @@ class Cylinder(Object3D):
                  radial_segments: int = 8,
                  height_segments: int = 1,
                  wireframe: bool = False,
-                 ):
+                 ) -> None:
         super().__init__('cylinder', top_radius, bottom_radius, height, radial_segments, height_segments, wireframe)
 
 
@@ -65,7 +64,7 @@ class Ring(Object3D):
                  theta_start: float = 0,
                  theta_length: float = 2 * np.pi,
                  wireframe: bool = False,
-                 ):
+                 ) -> None:
         super().__init__('ring',
                          inner_radius, outer_radius, theta_segments, phi_segments, theta_start, theta_length, wireframe)
 
@@ -81,7 +80,7 @@ class QuadraticBezierTube(Object3D):
                  radial_segments: int = 8,
                  closed: bool = False,
                  wireframe: bool = False,
-                 ):
+                 ) -> None:
         super().__init__('quadratic_bezier_tube',
                          start, mid, end, tubular_segments, radius, radial_segments, closed, wireframe)
 
@@ -92,7 +91,7 @@ class Extrusion(Object3D):
                  outline: List[List[float, float]],
                  height: float,
                  wireframe: bool = False,
-                 ):
+                 ) -> None:
         super().__init__('extrusion', outline, height, wireframe)
 
 
@@ -101,7 +100,7 @@ class Stl(Object3D):
     def __init__(self,
                  url: str,
                  wireframe: bool = False,
-                 ):
+                 ) -> None:
         super().__init__('stl', url, wireframe)
 
 
@@ -110,7 +109,7 @@ class Line(Object3D):
     def __init__(self,
                  start: List[float, float, float],
                  end: List[float, float, float],
-                 ):
+                 ) -> None:
         super().__init__('line', start, end)
 
 
@@ -122,7 +121,7 @@ class Curve(Object3D):
                  control2: List[float, float, float],
                  end: List[float, float, float],
                  num_points: int = 20,
-                 ):
+                 ) -> None:
         super().__init__('curve', start, control1, control2, end, num_points)
 
 
@@ -131,7 +130,7 @@ class Text(Object3D):
     def __init__(self,
                  text: str,
                  style: str = '',
-                 ):
+                 ) -> None:
         super().__init__('text', text, style)
 
 
@@ -140,7 +139,7 @@ class Text3d(Object3D):
     def __init__(self,
                  text: str,
                  style: str = '',
-                 ):
+                 ) -> None:
         super().__init__('text3d', text, style)
 
 
@@ -149,18 +148,16 @@ class Texture(Object3D):
     def __init__(self,
                  url: str,
                  coordinates: List[List[Optional[List[float]]]],
-                 ):
+                 ) -> None:
         super().__init__('texture', url, coordinates)
 
-    async def set_url(self, url: str):
+    def set_url(self, url: str) -> None:
         self.args[0] = url
-        for socket in WebPage.sockets.get(self.page.page_id, {}).values():
-            await self.view.run_method(f'set_texture_url("{self.id}", "{url}")', socket)
+        self.scene.run_method('set_texture_url', self.id, url)
 
-    async def set_coordinates(self, coordinates: List[List[Optional[List[float]]]]):
+    def set_coordinates(self, coordinates: List[List[Optional[List[float]]]]) -> None:
         self.args[1] = coordinates
-        for socket in WebPage.sockets.get(self.page.page_id, {}).values():
-            await self.view.run_method(f'set_texture_coordinates("{self.id}", {coordinates})', socket)
+        self.scene.run_method('set_texture_coordinates', self.id, coordinates)
 
 
 class SpotLight(Object3D):
@@ -172,5 +169,5 @@ class SpotLight(Object3D):
                  angle: float = np.pi / 3,
                  penumbra: float = 0.0,
                  decay: float = 1.0,
-                 ):
+                 ) -> None:
         super().__init__('spot_light', color, intensity, distance, angle, penumbra, decay)

+ 1 - 0
nicegui/ui.py

@@ -27,6 +27,7 @@ from .elements.progress import CircularProgress as circular_progress
 from .elements.progress import LinearProgress as linear_progress
 from .elements.radio import Radio as radio
 from .elements.row import Row as row
+from .elements.scene import Scene as scene
 from .elements.select import Select as select
 from .elements.separator import Separator as separator
 from .elements.slider import Slider as slider