-
-
Notifications
You must be signed in to change notification settings - Fork 610
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of github.com:zauberzeug/nicegui
- Loading branch information
Showing
10 changed files
with
396 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
import * as THREE from "three"; | ||
|
||
export default { | ||
template: ` | ||
<div style="position:relative"> | ||
<canvas style="position:relative"></canvas> | ||
</div>`, | ||
|
||
async mounted() { | ||
await this.$nextTick(); | ||
this.scene = getElement(this.scene_id).scene; | ||
|
||
if (this.camera_type === "perspective") { | ||
this.camera = new THREE.PerspectiveCamera( | ||
this.camera_params.fov, | ||
this.width / this.height, | ||
this.camera_params.near, | ||
this.camera_params.far | ||
); | ||
} else { | ||
this.camera = new THREE.OrthographicCamera( | ||
(-this.camera_params.size / 2) * (this.width / this.height), | ||
(this.camera_params.size / 2) * (this.width / this.height), | ||
this.camera_params.size / 2, | ||
-this.camera_params.size / 2, | ||
this.camera_params.near, | ||
this.camera_params.far | ||
); | ||
} | ||
this.look_at = new THREE.Vector3(0, 0, 0); | ||
this.camera.lookAt(this.look_at); | ||
this.camera.up = new THREE.Vector3(0, 0, 1); | ||
this.camera.position.set(0, -3, 5); | ||
|
||
this.renderer = undefined; | ||
try { | ||
this.renderer = new THREE.WebGLRenderer({ | ||
antialias: true, | ||
alpha: true, | ||
canvas: this.$el.children[0], | ||
}); | ||
} catch { | ||
this.$el.innerHTML = "Could not create WebGL renderer."; | ||
this.$el.style.width = this.width + "px"; | ||
this.$el.style.height = this.height + "px"; | ||
this.$el.style.padding = "10px"; | ||
this.$el.style.border = "1px solid silver"; | ||
return; | ||
} | ||
this.renderer.setClearColor("#eee"); | ||
this.renderer.setSize(this.width, this.height); | ||
|
||
this.$nextTick(() => this.resize()); | ||
window.addEventListener("resize", this.resize, false); | ||
|
||
const render = () => { | ||
requestAnimationFrame(() => setTimeout(() => render(), 1000 / 20)); | ||
TWEEN.update(); | ||
this.renderer.render(this.scene, this.camera); | ||
}; | ||
render(); | ||
|
||
const raycaster = new THREE.Raycaster(); | ||
const click_handler = (mouseEvent) => { | ||
let x = (mouseEvent.offsetX / this.renderer.domElement.width) * 2 - 1; | ||
let y = -(mouseEvent.offsetY / this.renderer.domElement.height) * 2 + 1; | ||
raycaster.setFromCamera({ x: x, y: y }, this.camera); | ||
this.$emit("click3d", { | ||
hits: raycaster | ||
.intersectObjects(this.scene.children, true) | ||
.filter((o) => o.object.object_id) | ||
.map((o) => ({ | ||
object_id: o.object.object_id, | ||
object_name: o.object.name, | ||
point: o.point, | ||
})), | ||
click_type: mouseEvent.type, | ||
button: mouseEvent.button, | ||
alt_key: mouseEvent.altKey, | ||
ctrl_key: mouseEvent.ctrlKey, | ||
meta_key: mouseEvent.metaKey, | ||
shift_key: mouseEvent.shiftKey, | ||
}); | ||
}; | ||
this.$el.onclick = click_handler; | ||
this.$el.ondblclick = click_handler; | ||
|
||
const connectInterval = setInterval(() => { | ||
if (window.socket.id === undefined) return; | ||
this.$emit("init", { socket_id: window.socket.id }); | ||
clearInterval(connectInterval); | ||
}, 100); | ||
}, | ||
|
||
beforeDestroy() { | ||
window.removeEventListener("resize", this.resize); | ||
}, | ||
|
||
methods: { | ||
move_camera(x, y, z, look_at_x, look_at_y, look_at_z, up_x, up_y, up_z, duration) { | ||
if (this.camera_tween) this.camera_tween.stop(); | ||
this.camera_tween = new TWEEN.Tween([ | ||
this.camera.position.x, | ||
this.camera.position.y, | ||
this.camera.position.z, | ||
this.camera.up.x, | ||
this.camera.up.y, | ||
this.camera.up.z, | ||
this.look_at.x, | ||
this.look_at.y, | ||
this.look_at.z, | ||
]) | ||
.to( | ||
[ | ||
x === null ? this.camera.position.x : x, | ||
y === null ? this.camera.position.y : y, | ||
z === null ? this.camera.position.z : z, | ||
up_x === null ? this.camera.up.x : up_x, | ||
up_y === null ? this.camera.up.y : up_y, | ||
up_z === null ? this.camera.up.z : up_z, | ||
look_at_x === null ? this.look_at.x : look_at_x, | ||
look_at_y === null ? this.look_at.y : look_at_y, | ||
look_at_z === null ? this.look_at.z : look_at_z, | ||
], | ||
duration * 1000 | ||
) | ||
.onUpdate((p) => { | ||
this.camera.position.set(p[0], p[1], p[2]); | ||
this.camera.up.set(p[3], p[4], p[5]); // NOTE: before calling lookAt | ||
this.look_at.set(p[6], p[7], p[8]); | ||
this.camera.lookAt(p[6], p[7], p[8]); | ||
}) | ||
.start(); | ||
}, | ||
resize() { | ||
const { clientWidth, clientHeight } = this.$el; | ||
this.renderer.setSize(clientWidth, clientHeight); | ||
this.camera.aspect = clientWidth / clientHeight; | ||
if (this.camera_type === "orthographic") { | ||
this.camera.left = (-this.camera.aspect * this.camera_params.size) / 2; | ||
this.camera.right = (this.camera.aspect * this.camera_params.size) / 2; | ||
} | ||
this.camera.updateProjectionMatrix(); | ||
}, | ||
}, | ||
|
||
props: { | ||
width: Number, | ||
height: Number, | ||
camera_type: String, | ||
camera_params: Object, | ||
scene_id: String, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import asyncio | ||
from typing import Any, Callable, Optional | ||
|
||
from typing_extensions import Self | ||
|
||
from ..awaitable_response import AwaitableResponse, NullResponse | ||
from ..element import Element | ||
from ..events import GenericEventArguments, SceneClickEventArguments, SceneClickHit, handle_event | ||
from .scene import Scene, SceneCamera | ||
|
||
|
||
class SceneView(Element, | ||
component='scene_view.js', | ||
libraries=['lib/tween/tween.umd.js'], | ||
exposed_libraries=['lib/three/three.module.js']): | ||
|
||
def __init__(self, | ||
scene: Scene, | ||
width: int = 400, | ||
height: int = 300, | ||
camera: Optional[SceneCamera] = None, | ||
on_click: Optional[Callable[..., Any]] = None, | ||
) -> None: | ||
"""Scene View | ||
Display an additional view of a 3D scene using `three.js <https://threejs.org/>`_. | ||
This component can only show a scene and not modify it. | ||
You can, however, independently move the camera. | ||
Current limitation: 2D and 3D text objects are not supported and will not be displayed in the scene view. | ||
:param scene: the scene which will be shown on the canvas | ||
:param width: width of the canvas | ||
:param height: height of the canvas | ||
:param camera: camera definition, either instance of ``ui.scene.perspective_camera`` (default) or ``ui.scene.orthographic_camera`` | ||
:param on_click: callback to execute when a 3D object is clicked | ||
""" | ||
super().__init__() | ||
self._props['width'] = width | ||
self._props['height'] = height | ||
self._props['scene_id'] = scene.id | ||
self.camera = camera or Scene.perspective_camera() | ||
self._props['camera_type'] = self.camera.type | ||
self._props['camera_params'] = self.camera.params | ||
self._click_handlers = [on_click] if on_click else [] | ||
self.is_initialized = False | ||
self.on('init', self._handle_init) | ||
self.on('click3d', self._handle_click) | ||
|
||
def on_click(self, callback: Callable[..., Any]) -> Self: | ||
"""Add a callback to be invoked when a 3D object is clicked.""" | ||
self._click_handlers.append(callback) | ||
return self | ||
|
||
def _handle_init(self, e: GenericEventArguments) -> None: | ||
self.is_initialized = True | ||
with self.client.individual_target(e.args['socket_id']): | ||
self.move_camera(duration=0) | ||
|
||
async def initialized(self) -> None: | ||
"""Wait until the scene is initialized.""" | ||
event = asyncio.Event() | ||
self.on('init', event.set, []) | ||
await self.client.connected() | ||
await event.wait() | ||
|
||
def run_method(self, name: str, *args: Any, timeout: float = 1, check_interval: float = 0.01) -> AwaitableResponse: | ||
if not self.is_initialized: | ||
return NullResponse() | ||
return super().run_method(name, *args, timeout=timeout, check_interval=check_interval) | ||
|
||
def _handle_click(self, e: GenericEventArguments) -> None: | ||
arguments = SceneClickEventArguments( | ||
sender=self, | ||
client=self.client, | ||
click_type=e.args['click_type'], | ||
button=e.args['button'], | ||
alt=e.args['alt_key'], | ||
ctrl=e.args['ctrl_key'], | ||
meta=e.args['meta_key'], | ||
shift=e.args['shift_key'], | ||
hits=[SceneClickHit( | ||
object_id=hit['object_id'], | ||
object_name=hit['object_name'], | ||
x=hit['point']['x'], | ||
y=hit['point']['y'], | ||
z=hit['point']['z'], | ||
) for hit in e.args['hits']], | ||
) | ||
for handler in self._click_handlers: | ||
handle_event(handler, arguments) | ||
|
||
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: | ||
"""Move the camera to a new position. | ||
:param x: camera x position | ||
:param y: camera y position | ||
:param z: camera z position | ||
:param look_at_x: camera look-at x position | ||
:param look_at_y: camera look-at y position | ||
:param look_at_z: camera look-at z position | ||
:param up_x: x component of the camera up vector | ||
:param up_y: y component of the camera up vector | ||
:param up_z: z component of the camera up vector | ||
:param duration: duration of the movement in seconds (default: `0.5`) | ||
""" | ||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.