-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
15e4a89
commit c80a2ae
Showing
2 changed files
with
296 additions
and
0 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
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() |
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,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(); | ||
} |