ngsolve-webgpu 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. ngsolve_webgpu/__init__.py +0 -0
  2. ngsolve_webgpu/_version.py +21 -0
  3. ngsolve_webgpu/animate.py +76 -0
  4. ngsolve_webgpu/cf.py +369 -0
  5. ngsolve_webgpu/clipping.py +155 -0
  6. ngsolve_webgpu/geometry.py +376 -0
  7. ngsolve_webgpu/isosurface.py +101 -0
  8. ngsolve_webgpu/jupyter.py +168 -0
  9. ngsolve_webgpu/lic.py +115 -0
  10. ngsolve_webgpu/mesh.py +424 -0
  11. ngsolve_webgpu/shaders/clipping/common.wgsl +120 -0
  12. ngsolve_webgpu/shaders/clipping/compute.wgsl +30 -0
  13. ngsolve_webgpu/shaders/clipping/render.wgsl +37 -0
  14. ngsolve_webgpu/shaders/compute.wgsl +59 -0
  15. ngsolve_webgpu/shaders/elements3d.wgsl +83 -0
  16. ngsolve_webgpu/shaders/eval/common.wgsl +14 -0
  17. ngsolve_webgpu/shaders/eval/seg.wgsl +30 -0
  18. ngsolve_webgpu/shaders/eval/tet.wgsl +61 -0
  19. ngsolve_webgpu/shaders/eval/trig.wgsl +121 -0
  20. ngsolve_webgpu/shaders/eval.wgsl +0 -0
  21. ngsolve_webgpu/shaders/geo_edge.wgsl +55 -0
  22. ngsolve_webgpu/shaders/geo_face.wgsl +47 -0
  23. ngsolve_webgpu/shaders/geo_vertex.wgsl +71 -0
  24. ngsolve_webgpu/shaders/isosurface/compute.wgsl +63 -0
  25. ngsolve_webgpu/shaders/isosurface/negative_clipping.wgsl +13 -0
  26. ngsolve_webgpu/shaders/isosurface/negative_surface.wgsl +15 -0
  27. ngsolve_webgpu/shaders/isosurface/render.wgsl +50 -0
  28. ngsolve_webgpu/shaders/line_integral_convolution.wgsl +100 -0
  29. ngsolve_webgpu/shaders/mesh.wgsl +296 -0
  30. ngsolve_webgpu/shaders/numbers.wgsl +40 -0
  31. ngsolve_webgpu/shaders/shader.wgsl +111 -0
  32. ngsolve_webgpu/shaders/uniforms.wgsl +93 -0
  33. ngsolve_webgpu-0.0.1.dist-info/METADATA +13 -0
  34. ngsolve_webgpu-0.0.1.dist-info/RECORD +37 -0
  35. ngsolve_webgpu-0.0.1.dist-info/WHEEL +5 -0
  36. ngsolve_webgpu-0.0.1.dist-info/licenses/LICENSE +504 -0
  37. ngsolve_webgpu-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,376 @@
1
+ import webgpu
2
+ from webgpu.render_object import MultipleRenderObject, RenderObject
3
+ from webgpu.utils import (
4
+ buffer_from_array,
5
+ create_bind_group,
6
+ ReadBuffer,
7
+ uniform_from_array,
8
+ )
9
+ from webgpu.webgpu_api import *
10
+ from webgpu.clipping import Clipping
11
+
12
+ import numpy as np
13
+
14
+
15
+ class Binding:
16
+ VERTICES = 90
17
+ NORMALS = 91
18
+ INDICES = 92
19
+ COLORS = 93
20
+
21
+
22
+ class GeometryFaceRenderer(RenderObject):
23
+ n_vertices: int = 3
24
+ depthBias: int = 1
25
+ clipping: Clipping | None = None
26
+
27
+ def __init__(self, geo):
28
+ super().__init__(label="GeometryFaces")
29
+ self.geo = geo
30
+ self.colors = None
31
+ self.active = True
32
+
33
+ def get_bounding_box(self):
34
+ return self.bounding_box
35
+
36
+ def set_colors(self, colors):
37
+ """colors is numpy float32 array with 4 times number indices entries"""
38
+ self.colors = colors
39
+ if "colors" in self._buffers:
40
+ self.device.queue.writeBuffer(
41
+ self._buffers["colors"], 0, self.colors.tobytes()
42
+ )
43
+
44
+ def update(self, vis_data):
45
+ self.bounding_box = (vis_data["min"], vis_data["max"])
46
+ verts = vis_data["vertices"]
47
+ self.n_instances = len(verts) // 6
48
+ normals = vis_data["normals"]
49
+ indices = vis_data["indices"]
50
+ if self.colors is None:
51
+ self.colors = vis_data["face_colors"]
52
+ self._buffers = {}
53
+ for key, data in zip(
54
+ ("vertices", "normals", "indices", "colors"),
55
+ (verts, normals, indices, self.colors),
56
+ ):
57
+ self._buffers[key] = buffer_from_array(data)
58
+ self.create_render_pipeline()
59
+
60
+ def get_bindings(self):
61
+ return [
62
+ *self.options.camera.get_bindings(),
63
+ webgpu.BufferBinding(Binding.VERTICES, self._buffers["vertices"]),
64
+ webgpu.BufferBinding(Binding.NORMALS, self._buffers["normals"]),
65
+ webgpu.BufferBinding(Binding.INDICES, self._buffers["indices"]),
66
+ webgpu.BufferBinding(Binding.COLORS, self._buffers["colors"]),
67
+ ]
68
+
69
+ def get_shader_code(self):
70
+ shader_code = ""
71
+ shader_code += webgpu.read_shader_file(f"geo_face.wgsl", __file__)
72
+ shader_code += self.options.camera.get_shader_code()
73
+ shader_code += self.options.light.get_shader_code()
74
+ shader_code += self.clipping.get_shader_code()
75
+ return shader_code
76
+
77
+ def pick_index_render(self, encoder, texture, depth_texture, load_op):
78
+ texture_format = TextureFormat.rg32uint
79
+ target = ColorTargetState(format=texture_format)
80
+ shader_module = self.device.createShaderModule(self.get_shader_code())
81
+ layout, group = create_bind_group(self.device, self.get_bindings())
82
+ playout = self.device.createPipelineLayout([layout])
83
+ pipeline = self.device.createRenderPipeline(
84
+ layout=playout,
85
+ vertex=VertexState(
86
+ module=shader_module, entryPoint=self.vertex_entry_point
87
+ ),
88
+ fragment=FragmentState(
89
+ module=shader_module,
90
+ entryPoint="fragmentQueryIndex",
91
+ targets=[target],
92
+ ),
93
+ primitive=PrimitiveState(topology=self.topology),
94
+ depthStencil=DepthStencilState(
95
+ format=self.options.canvas.depth_format,
96
+ depthWriteEnabled=True,
97
+ depthCompare=CompareFunction.less,
98
+ depthBias=self.depthBias,
99
+ ),
100
+ multisample=MultisampleState(count=1),
101
+ )
102
+ render_pass = encoder.beginRenderPass(
103
+ colorAttachments=[
104
+ RenderPassColorAttachment(
105
+ view=texture.createView(), clearValue=Color(0, 3), loadOp=load_op
106
+ )
107
+ ],
108
+ depthStencilAttachment=RenderPassDepthStencilAttachment(
109
+ view=depth_texture.createView(),
110
+ depthClearValue=1.0,
111
+ depthLoadOp=load_op,
112
+ ),
113
+ )
114
+ render_pass.setPipeline(pipeline)
115
+ render_pass.setBindGroup(0, group)
116
+ render_pass.draw(self.n_vertices, self.n_instances)
117
+ render_pass.end()
118
+
119
+
120
+ class GeometryEdgeRenderer(RenderObject):
121
+ n_vertices: int = 4
122
+ topology: PrimitiveTopology = PrimitiveTopology.triangle_strip
123
+ clipping: Clipping | None = None
124
+
125
+ def __init__(self, geo):
126
+ self.geo = geo
127
+ super().__init__(label="GeometryEdges")
128
+ self.active = True
129
+ self.thickness = 0.02
130
+
131
+ def set_colors(self, colors):
132
+ """colors is numpy float32 array with 4 times number indices entries"""
133
+ self.colors = colors
134
+ if "colors" in self._buffers:
135
+ self.device.queue.writeBuffer(
136
+ self._buffers["colors"], 0, self.colors.tobytes()
137
+ )
138
+
139
+ def update(self, vis_data):
140
+ verts = vis_data["edges"]
141
+ self.colors = vis_data["edge_colors"]
142
+ self.n_instances = len(verts) // 6
143
+ self.thickness_uniform = uniform_from_array(
144
+ np.array([self.thickness], dtype=np.float32)
145
+ )
146
+ self._buffers = {}
147
+ self._buffers["vertices"] = buffer_from_array(verts)
148
+ self._buffers["colors"] = buffer_from_array(self.colors)
149
+ self._buffers["index"] = buffer_from_array(vis_data["edge_indices"])
150
+ self.create_render_pipeline()
151
+
152
+ def get_shader_code(self):
153
+ shader_code = ""
154
+ shader_code += webgpu.read_shader_file(f"geo_edge.wgsl", __file__)
155
+ shader_code += self.options.camera.get_shader_code()
156
+ shader_code += self.clipping.get_shader_code()
157
+ return shader_code
158
+
159
+ def get_bindings(self):
160
+ return [
161
+ *self.options.camera.get_bindings(),
162
+ webgpu.BufferBinding(90, self._buffers["vertices"]),
163
+ webgpu.BufferBinding(91, self._buffers["colors"]),
164
+ webgpu.UniformBinding(92, self.thickness_uniform),
165
+ webgpu.BufferBinding(93, self._buffers["index"]),
166
+ ]
167
+
168
+ def pick_index_render(self, encoder, texture, depth_texture, load_op):
169
+ texture_format = TextureFormat.rg32uint
170
+ target = ColorTargetState(format=texture_format)
171
+ shader_module = self.device.createShaderModule(self.get_shader_code())
172
+ layout, group = create_bind_group(self.device, self.get_bindings())
173
+ playout = self.device.createPipelineLayout([layout])
174
+ pipeline = self.device.createRenderPipeline(
175
+ layout=playout,
176
+ vertex=VertexState(
177
+ module=shader_module, entryPoint=self.vertex_entry_point
178
+ ),
179
+ fragment=FragmentState(
180
+ module=shader_module,
181
+ entryPoint="fragmentQueryIndex",
182
+ targets=[target],
183
+ ),
184
+ primitive=PrimitiveState(topology=self.topology),
185
+ depthStencil=DepthStencilState(
186
+ format=self.options.canvas.depth_format,
187
+ depthWriteEnabled=True,
188
+ depthCompare=CompareFunction.less,
189
+ depthBias=self.depthBias,
190
+ ),
191
+ multisample=MultisampleState(count=1),
192
+ )
193
+ render_pass = encoder.beginRenderPass(
194
+ colorAttachments=[
195
+ RenderPassColorAttachment(
196
+ view=texture.createView(), clearValue=Color(0, 3), loadOp=load_op
197
+ )
198
+ ],
199
+ depthStencilAttachment=RenderPassDepthStencilAttachment(
200
+ view=depth_texture.createView(),
201
+ depthClearValue=1.0,
202
+ depthLoadOp=load_op,
203
+ ),
204
+ )
205
+ render_pass.setPipeline(pipeline)
206
+ render_pass.setBindGroup(0, group)
207
+ render_pass.draw(self.n_vertices, self.n_instances)
208
+ render_pass.end()
209
+
210
+
211
+ class GeometryVertexRenderer(RenderObject):
212
+ n_vertices: int = 4
213
+ topology: PrimitiveTopology = PrimitiveTopology.triangle_strip
214
+ depthBias: int = 0
215
+ clipping: Clipping | None = None
216
+
217
+ def __init__(self, geo):
218
+ self.geo = geo
219
+ super().__init__(label="GeometryVertices")
220
+ self.active = True
221
+ self.thickness = 0.05
222
+
223
+ def set_colors(self, colors):
224
+ """colors is numpy float32 array with 4 times number indices entries"""
225
+ self.colors = colors
226
+ if "colors" in self._buffers:
227
+ self.device.queue.writeBuffer(
228
+ self._buffers["colors"], 0, self.colors.tobytes()
229
+ )
230
+
231
+ def get_shader_code(self):
232
+ shader_code = ""
233
+ shader_code += webgpu.read_shader_file(f"geo_vertex.wgsl", __file__)
234
+ shader_code += self.clipping.get_shader_code()
235
+ shader_code += self.options.camera.get_shader_code()
236
+ return shader_code
237
+
238
+ def update(self, vis_data):
239
+ verts = set(self.geo.shape.vertices)
240
+ self.colors = np.array(
241
+ [v.col if v.col is not None else [0.3, 0.3, 0.3, 1.0] for v in verts],
242
+ dtype=np.float32,
243
+ ).flatten()
244
+ self.n_instances = len(verts)
245
+ vert_values = np.array(
246
+ [[pi for pi in v.p] for v in verts], dtype=np.float32
247
+ ).flatten()
248
+ self._buffers = {}
249
+ self._buffers["vertices"] = buffer_from_array(vert_values)
250
+ self._buffers["colors"] = buffer_from_array(self.colors)
251
+ self.thickness_uniform = uniform_from_array(
252
+ np.array([self.thickness], dtype=np.float32)
253
+ )
254
+ self.create_render_pipeline()
255
+
256
+ def get_bindings(self):
257
+ return [
258
+ *self.options.camera.get_bindings(),
259
+ webgpu.BufferBinding(90, self._buffers["vertices"]),
260
+ webgpu.BufferBinding(91, self._buffers["colors"]),
261
+ webgpu.UniformBinding(92, self.thickness_uniform),
262
+ ]
263
+
264
+ def pick_index_render(self, encoder, texture, depth_texture, load_op):
265
+ texture_format = TextureFormat.rg32uint
266
+ target = ColorTargetState(format=texture_format)
267
+ shader_module = self.device.createShaderModule(self.get_shader_code())
268
+ layout, group = create_bind_group(self.device, self.get_bindings())
269
+ playout = self.device.createPipelineLayout([layout])
270
+ pipeline = self.device.createRenderPipeline(
271
+ layout=playout,
272
+ vertex=VertexState(
273
+ module=shader_module, entryPoint=self.vertex_entry_point
274
+ ),
275
+ fragment=FragmentState(
276
+ module=shader_module,
277
+ entryPoint="fragmentQueryIndex",
278
+ targets=[target],
279
+ ),
280
+ primitive=PrimitiveState(topology=self.topology),
281
+ depthStencil=DepthStencilState(
282
+ format=self.options.canvas.depth_format,
283
+ depthWriteEnabled=True,
284
+ depthCompare=CompareFunction.less,
285
+ depthBias=self.depthBias,
286
+ ),
287
+ multisample=MultisampleState(count=1),
288
+ )
289
+ render_pass = encoder.beginRenderPass(
290
+ colorAttachments=[
291
+ RenderPassColorAttachment(
292
+ view=texture.createView(), clearValue=Color(0, 3), loadOp=load_op
293
+ )
294
+ ],
295
+ depthStencilAttachment=RenderPassDepthStencilAttachment(
296
+ view=depth_texture.createView(),
297
+ depthClearValue=1.0,
298
+ depthLoadOp=load_op,
299
+ ),
300
+ )
301
+ render_pass.setPipeline(pipeline)
302
+ render_pass.setBindGroup(0, group)
303
+ render_pass.draw(self.n_vertices, self.n_instances)
304
+ render_pass.end()
305
+
306
+
307
+ class GeometryRenderObject(MultipleRenderObject):
308
+ def __init__(self, geo, label="Geometry"):
309
+ self.geo = geo
310
+ self.faces = GeometryFaceRenderer(geo)
311
+ self.edges = GeometryEdgeRenderer(geo)
312
+ self.vertices = GeometryVertexRenderer(geo)
313
+ self.clipping = Clipping()
314
+ self.faces.clipping = self.clipping
315
+ self.edges.clipping = self.clipping
316
+ self.vertices.clipping = self.clipping
317
+ super().__init__([self.vertices, self.edges, self.faces])
318
+
319
+ def update(self, timestamp):
320
+ if timestamp == self._timestamp:
321
+ return
322
+ self._timestamp = timestamp
323
+ vis_data = self.geo._visualizationData()
324
+ self.bounding_box = (vis_data["min"] + 1e-7, vis_data["max"] - 1e-7)
325
+
326
+ for ro in self.render_objects:
327
+ ro.options = self.options
328
+ ro.update(vis_data)
329
+
330
+ def get_bounding_box(self):
331
+ return self.bounding_box
332
+
333
+ def render(self, encoder):
334
+ for r in self.render_objects:
335
+ if r.active:
336
+ r.render(encoder)
337
+
338
+ def pick_index(self, mouseX, mouseY):
339
+ rect = self.canvas.canvas.getBoundingClientRect()
340
+ mouseX -= rect.x
341
+ mouseY -= int(rect.y)
342
+ texture_format = TextureFormat.rg32uint
343
+ read_size = 8
344
+ texture = self.device.createTexture(
345
+ size=[rect.width, rect.height, 1],
346
+ sampleCount=1,
347
+ format=texture_format,
348
+ usage=TextureUsage.COPY_SRC | TextureUsage.RENDER_ATTACHMENT,
349
+ )
350
+ depth_texture = self.device.createTexture(
351
+ size=[rect.width, rect.height, 1],
352
+ format=self.canvas.depth_format,
353
+ usage=TextureUsage.RENDER_ATTACHMENT,
354
+ sampleCount=1,
355
+ )
356
+ read_buffer = self.device.createBuffer(
357
+ read_size, BufferUsage.MAP_READ | BufferUsage.COPY_DST
358
+ )
359
+ encoder = self.device.createCommandEncoder()
360
+
361
+ load_op = LoadOp.clear
362
+ for ro in self.render_objects:
363
+ if ro.active:
364
+ ro.pick_index_render(encoder, texture, depth_texture, load_op)
365
+ load_op = LoadOp.load
366
+ encoder.copyTextureToBuffer(
367
+ TexelCopyTextureInfo(texture, origin=Origin3d(mouseX, mouseY, 0)),
368
+ TexelCopyBufferInfo(TexelCopyBufferLayout(1), read_buffer),
369
+ [1, 1, 1],
370
+ )
371
+ self.device.queue.submit([encoder.finish()])
372
+ read_buffer.handle.mapAsync(MapMode.READ, 0, read_size)
373
+ result = np.frombuffer(
374
+ read_buffer.handle.getMappedRange(0, read_size), dtype=np.uint32
375
+ )
376
+ return result
@@ -0,0 +1,101 @@
1
+ import numpy as np
2
+ from webgpu import (
3
+ BufferBinding,
4
+ Colormap,
5
+ Clipping,
6
+ read_shader_file,
7
+ )
8
+ from webgpu.utils import UniformBinding, uniform_from_array
9
+ from .clipping import ClippingCF
10
+ from .cf import CFRenderer
11
+
12
+
13
+ class IsoSurfaceRenderObject(ClippingCF):
14
+ compute_shader = "isosurface/compute.wgsl"
15
+ vertex_entry_point = "vertex_isosurface"
16
+ fragment_entry_point = "fragment_isosurface"
17
+
18
+ def __init__(self, func_data, levelset_data):
19
+ super().__init__(func_data)
20
+ self.levelset = levelset_data
21
+ self.levelset.need_3d = True
22
+ self.colormap = Colormap()
23
+ self.clipping = Clipping()
24
+ self.subdivision = 0
25
+
26
+ def get_shader_code(self, compute=False):
27
+ code = super().get_shader_code(compute=compute)
28
+ if not compute:
29
+ code += read_shader_file("isosurface/render.wgsl", __file__)
30
+ return code
31
+
32
+ def update(self, timestamp):
33
+ if timestamp == self._timestamp:
34
+ return
35
+ self.uniform_subdiv = uniform_from_array(
36
+ np.array([self.subdivision], dtype=np.uint32)
37
+ )
38
+ self.levelset.update(timestamp)
39
+ self.levelset_buffer = self.levelset.get_buffers()["data_3d"]
40
+ super().update(timestamp)
41
+
42
+ def get_bindings(self, compute=False):
43
+ bindings = super().get_bindings(compute)
44
+ if compute:
45
+ bindings.append(UniformBinding(27, self.uniform_subdiv))
46
+ bindings += [
47
+ BufferBinding(26, self.levelset_buffer),
48
+ ]
49
+ return bindings
50
+
51
+
52
+ class NegativeSurfaceRenderer(CFRenderer):
53
+ def __init__(self, functiondata, levelsetdata):
54
+ super().__init__(functiondata, label="NegativeSurfaceRenderer")
55
+ self.fragment_entry_point = "fragmentCheckLevelset"
56
+ self.levelset = levelsetdata
57
+
58
+ def update(self, timestamp):
59
+ if timestamp == self._timestamp:
60
+ return
61
+ self.levelset.update(timestamp)
62
+ buffers = self.levelset.get_buffers()
63
+ self.levelset_buffer = buffers["data_2d"]
64
+ super().update(timestamp)
65
+
66
+ def get_bindings(self):
67
+ return super().get_bindings() + [BufferBinding(80, self.levelset_buffer)]
68
+
69
+ def get_shader_code(self):
70
+ return super().get_shader_code() + read_shader_file(
71
+ "isosurface/negative_surface.wgsl", __file__
72
+ )
73
+
74
+
75
+ class NegativeClippingRenderer(ClippingCF):
76
+ fragment_entry_point = "fragment_neg_clip"
77
+
78
+ def __init__(self, data, levelsetdata):
79
+ super().__init__(data)
80
+ self.levelset = levelsetdata
81
+ self.levelset.need_3d = True
82
+
83
+ def update(self, timestamp):
84
+ if self._timestamp == timestamp:
85
+ return
86
+ self.levelset.update(timestamp)
87
+ buffers = self.levelset.get_buffers()
88
+ self.levelset_buffer = buffers["data_3d"]
89
+ super().update(timestamp)
90
+
91
+ def get_bindings(self, compute=False):
92
+ bindings = super().get_bindings(compute)
93
+ if not compute:
94
+ bindings += [BufferBinding(80, self.levelset_buffer)]
95
+ return bindings
96
+
97
+ def get_shader_code(self, compute=False):
98
+ code = super().get_shader_code(compute)
99
+ if not compute:
100
+ code += read_shader_file("isosurface/negative_clipping.wgsl", __file__)
101
+ return code
@@ -0,0 +1,168 @@
1
+ import ngsolve as ngs
2
+ import webgpu.jupyter as wj
3
+
4
+ _local_path = None # change this to local path of pyodide compiled zip files
5
+
6
+ if not wj._is_pyodide:
7
+ from IPython.display import Javascript, display
8
+
9
+ def run_on_pyodide_ready(code):
10
+ display(
11
+ Javascript(
12
+ f"""
13
+ function waitTillPyodideReady() {{
14
+ window.webgpu_ready = new Promise((resolve, reject) => {{
15
+ if(window.pyodide_ready === undefined) {{
16
+ window.setTimeout(waitTillPyodideReady, 100);
17
+ }} else {{
18
+ window.pyodide_ready.then(() => {{
19
+ window.pyodide.runPythonAsync(`{code}`).then(() => {{
20
+ resolve();
21
+ }});
22
+ }});
23
+ }}
24
+ }});
25
+ }}
26
+ waitTillPyodideReady();
27
+ """
28
+ )
29
+ )
30
+
31
+ if _local_path is None:
32
+ run_on_pyodide_ready(
33
+ """
34
+ _NGSOLVE_BASE_URL = "https://ngsolve.org/files/pyodide-0.27.2/master/"
35
+ print("run code")
36
+ import micropip
37
+ from pyodide.http import pyfetch
38
+ import pyodide_js
39
+ from pyodide._package_loader import unpack_buffer
40
+
41
+ await micropip.install("scipy")
42
+ import scipy
43
+
44
+ for module in ["pyngcore", "netgen", "ngsolve"]:
45
+ response = await pyfetch(f"{_NGSOLVE_BASE_URL}/{module}.zip")
46
+ data = await response.buffer()
47
+ dynlibs = list(
48
+ unpack_buffer(
49
+ data, format="zip", filename=f"{module}.zip", calculate_dynlibs=True
50
+ )
51
+ )
52
+ for lib in dynlibs:
53
+ await pyodide_js._api.loadDynlib(lib, True, [])
54
+ print("loaded ", module)
55
+ """
56
+ )
57
+ else:
58
+
59
+ def local_install(local_packages):
60
+ packages = []
61
+ for package in local_packages:
62
+ with open(_local_path + f"/{package}.zip", "rb") as f:
63
+ data = f.read()
64
+ packages.append((package, data))
65
+ packages = wj._encode_data(packages)
66
+ run_on_pyodide_ready(
67
+ f"""
68
+ import shutil
69
+ from pyodide._package_loader import get_dynlibs
70
+ import pyodide_js
71
+ from pathlib import Path
72
+ import webgpu.jupyter as wj
73
+ import micropip
74
+ await micropip.install('scipy')
75
+ import scipy
76
+ for package, data in wj._decode_data('{packages}'):
77
+ with open(package + '.zip', 'wb') as f:
78
+ f.write(data)
79
+ import os
80
+ print("local files = ", os.listdir('.'))
81
+ shutil.unpack_archive(package + '.zip', '.', 'zip')
82
+ print("after local files = ", os.listdir('.'))
83
+ libs = get_dynlibs(package + '.zip', '.zip', Path('.'))
84
+ print('got libs = ', libs)
85
+ for lib in libs:
86
+ await pyodide_js._api.loadDynlib(lib, True, [])
87
+ import importlib
88
+ print('import package = ', package)
89
+ importlib.import_module(package)
90
+ """
91
+ )
92
+
93
+ local_install(["pyngcore", "netgen", "ngsolve"])
94
+
95
+
96
+ def Draw(
97
+ obj: ngs.CoefficientFunction | ngs.Mesh,
98
+ mesh: ngs.Mesh | None = None,
99
+ name: str = None,
100
+ width=600,
101
+ height=600,
102
+ order: int = 2,
103
+ vectors=None,
104
+ **kwargs,
105
+ ):
106
+ """
107
+ NGSolve Draw command. Draws a CoefficientFunction or a Mesh with a set of options using the NGSolve webgpu framework.
108
+
109
+ Parameters
110
+ ----------
111
+
112
+ obj : ngs.CoefficientFunction | ngs.Mesh
113
+ The CoefficientFunction or Mesh to draw.
114
+
115
+ mesh : ngs.Mesh | None
116
+ The mesh to draw. If obj is a CoefficientFunction, mesh is required.
117
+
118
+ width : int
119
+ The width of the canvas.
120
+
121
+ height : int
122
+ The height of the canvas.
123
+
124
+ order : int
125
+ The order which is used to render the CoefficientFunction. Default is 2.
126
+ """
127
+ # create gui before calling render
128
+ render_objects = []
129
+ if isinstance(obj, ngs.Mesh | ngs.Region):
130
+ mesh = obj
131
+ from .mesh import MeshData, Mesh2dElementsRenderer, Mesh2dWireframeRenderer
132
+
133
+ mesh_data = MeshData(mesh)
134
+ m2d = Mesh2dElementsRenderer(mesh_data)
135
+ wf = Mesh2dWireframeRenderer(mesh_data)
136
+ render_objects.append(m2d)
137
+ render_objects.append(wf)
138
+ if isinstance(obj, ngs.CoefficientFunction):
139
+ if mesh is None:
140
+ if isinstance(mesh, ngs.GridFunction):
141
+ mesh = mesh.space.mesh
142
+ else:
143
+ raise ValueError("If obj is a CoefficientFunction, mesh is required.")
144
+ from .cf import CoefficientFunctionRenderObject, FunctionData
145
+ from .mesh import MeshData, Mesh2dWireframeRenderer
146
+
147
+ mesh_data = MeshData(mesh)
148
+ function_data = FunctionData(mesh_data, obj, order)
149
+ r_cf = CoefficientFunctionRenderObject(function_data)
150
+ wf = Mesh2dWireframeRenderer(mesh_data)
151
+ render_objects.append(r_cf)
152
+ render_objects.append(wf)
153
+ render_objects.append(r_cf.colormap)
154
+ if vectors:
155
+ options = vectors if isinstance(vectors, dict) else {}
156
+ if mesh.dim != 2:
157
+ raise ValueError("Vectors currently only implemented on 2d meshes")
158
+ from .cf import VectorCFRenderer
159
+
160
+ vcf = VectorCFRenderer(obj, mesh, **options)
161
+ vcf.colormap = r_cf.colormap
162
+ render_objects.append(vcf)
163
+
164
+ scene = wj.Scene(render_objects)
165
+ scene = wj.Draw(scene, width, height, modules=["ngsolve_webgpu"])
166
+ for r in render_objects:
167
+ r.add_options_to_gui(scene.gui)
168
+ return scene