Skip to content

Commit

Permalink
nicegui example
Browse files Browse the repository at this point in the history
  • Loading branch information
mhochsteger committed Oct 25, 2024
1 parent 15e4a89 commit c80a2ae
Show file tree
Hide file tree
Showing 2 changed files with 296 additions and 0 deletions.
161 changes: 161 additions & 0 deletions nicegui/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
import asyncio
from typing import Optional

import base64
import marshal
from typing_extensions import Self

from nicegui import app, ui
from nicegui.element import Element
from nicegui.events import (
GenericEventArguments,
Handler,
SceneClickEventArguments,
SceneClickHit,
SceneDragEventArguments,
handle_event,
)

app.add_static_files("/pyodide", "../pyodide")
app.add_static_files("/webgpu", "../webgpu")


class WebGPUScene(
Element,
component="webgpu_scene.js",
dependencies=[
"./pyodide/pyodide.js",
],
default_classes="",
):
# pylint: disable=import-outside-toplevel

def __init__(
self,
width: int = 400,
height: int = 300,
on_click: Optional[Handler[SceneClickEventArguments]] = None,
on_drag_start: Optional[Handler[SceneDragEventArguments]] = None,
on_drag_end: Optional[Handler[SceneDragEventArguments]] = None,
background_color: str = "#eee",
) -> None:
"""Webgpu scene."""
super().__init__()
self._props["width"] = width
self._props["height"] = height
self._props["background_color"] = background_color

self.on("init", self._handle_init)
self.on("click3d", self._handle_click)
self.on("dragstart", self._handle_drag)
self.on("dragend", self._handle_drag)
self._click_handlers = [on_click] if on_click else []
self._drag_start_handlers = [on_drag_start] if on_drag_start else []
self._drag_end_handlers = [on_drag_end] if on_drag_end else []
self.python_expression = ""

def on_click(self, callback: Handler[SceneClickEventArguments]) -> Self:
"""Add a callback to be invoked when a 3D object is clicked."""
self._click_handlers.append(callback)
return self

def on_drag_start(self, callback: Handler[SceneDragEventArguments]) -> Self:
"""Add a callback to be invoked when a 3D object is dragged."""
self._drag_start_handlers.append(callback)
return self

def on_drag_end(self, callback: Handler[SceneDragEventArguments]) -> Self:
"""Add a callback to be invoked when a 3D object is dropped."""
self._drag_end_handlers.append(callback)
return self

def __enter__(self) -> Self:
print("create scene")
super().__enter__()
return self

def _handle_init(self, e: GenericEventArguments) -> None:
pass

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 _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 _handle_drag(self, e: GenericEventArguments) -> None:
return

def __len__(self) -> int:
return 1

def _handle_delete(self) -> None:
# binding.remove(list(self.objects.values()))
super()._handle_delete()

def clear(self) -> None:
"""Remove all objects from the scene."""
super().clear()

def redraw(self, code):
func = marshal.dumps(draw_function.__code__)
func = base64.b64encode(func).decode("utf-8")
data = [func, code]
self.run_method("run_user_function", data)


ui.add_head_html('<script type="text/javascript" src="./pyodide/pyodide.js"></script>')

inp = ui.input("function")
button = ui.button("update")
scene = WebGPUScene(width=1024, height=800)
button.on_click(lambda _: scene.redraw(inp.value))


def draw_function(expr):
import js
import ngsolve as ngs
import webgpu.main
import webgpu.mesh
from webgpu.main import gpu

mesh = ngs.Mesh(ngs.unit_square.GenerateMesh(maxh=0.5))
order = 6
region = mesh.Region(ngs.VOL)
cf = eval(expr, globals() | ngs.__dict__, locals())
n_trigs, buffers = webgpu.mesh.create_mesh_buffers(gpu.device, region)
buffers = buffers | webgpu.mesh.create_function_value_buffers(
gpu.device, cf, region, order
)
mesh_object = webgpu.mesh.MeshRenderObject(gpu, buffers, n_trigs)
webgpu.main.mesh_object = mesh_object
js.requestAnimationFrame(webgpu.main.render_function)


ui.run()
135 changes: 135 additions & 0 deletions nicegui/webgpu_scene.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
console.log("loaded webgui_scene.js");
export default {
template: `
<div style="position:relative;" data-initializing>
<canvas id="canvas" style=""></canvas>
<div style="position:absolute;pointer-events:none;top:0"></div>
<div style="position:absolute;pointer-events:none;top:0"></div>
</div>`,

mounted() {
const canvas = this.$el.querySelector("canvas");
canvas.width = this.width;
canvas.height = this.height;
console.log("mounted");
main();
this.is_initialized = false;
},

beforeDestroy() {
//window.removeEventListener("resize", this.resize);
//window.removeEventListener("DOMContentLoaded", this.resize);
},

methods: {
create(type, id, parent_id, ...args) {
if (!this.is_initialized) return;
},
name(object_id, name) {
return "";
},
move(object_id, x, y, z) {},
scale(object_id, sx, sy, sz) {},
rotate(object_id, R) {},
visible(object_id, value) {},
delete(object_id) {},
resize() {
const { clientWidth, clientHeight } = this.$el;
//this.renderer.setSize(clientWidth, clientHeight);
},
init_objects(data) {
this.resize();
this.$el.removeAttribute("data-initializing");
this.is_initialized = true;
},

async run_user_function(data) {
const user_function = pyodide.runPython(
"import webgpu.main; webgpu.main.user_function",
);
await user_function(data);
},
},

props: {
width: Number,
height: Number,
click_events: Array,
background_color: String,
},
};

let pyodide = null;
console.log("load init_webgpu.js");

const files = [
"__init__.py",
"colormap.py",
"compute.wgsl",
"eval.wgsl",
"gpu.py",
"input_handler.py",
"main.py",
"mesh.py",
"shader.wgsl",
"uniforms.py",
"utils.py",
];

async function reload() {
try {
pyodide.FS.mkdir("webgpu");
} catch {}
for (var file of files) {
const data = await (
await fetch(`./webgpu/${file}`, { method: "GET", cache: "no-cache" })
).text();

pyodide.FS.writeFile("webgpu/" + file, data);
}
await pyodide.runPythonAsync(
"import webgpu.main; await webgpu.main.reload();",
);
}

async function main() {
console.log("loading pyodide", performance.now());
// const blob = await (await fetch("./pyodide/snapshot.bin")).blob();
// const decompressor = new DecompressionStream('gzip');
// const stream = blob.stream().pipeThrough(decompressor);
// const response = new Response(stream);
// return await response.arrayBuffer();

pyodide = await loadPyodide({
// _loadSnapshot: blob.arrayBuffer(),
});

pyodide.setDebug(true);
console.log("loaded pyodide", performance.now());
console.log(pyodide);
await pyodide.loadPackage([
"netgen",
"ngsolve",
"packaging",
"numpy",
"micropip",
]);
await pyodide.runPythonAsync(
"import micropip; await micropip.install('dill');",
);
console.log("loaded netgen", performance.now());

//try {
// const socket = new WebSocket("ws://localhost:6789");
// socket.addEventListener("open", function (event) {
// console.log("WebSocket connection opened");
// });
// socket.addEventListener("message", function (event) {
// console.log("Message from server ", event.data);
// reload();
// });
//} catch {
// console.log("WebSocket connection failed");
//}
reload();
}

0 comments on commit c80a2ae

Please sign in to comment.