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.
- ngsolve_webgpu/__init__.py +0 -0
- ngsolve_webgpu/_version.py +21 -0
- ngsolve_webgpu/animate.py +76 -0
- ngsolve_webgpu/cf.py +369 -0
- ngsolve_webgpu/clipping.py +155 -0
- ngsolve_webgpu/geometry.py +376 -0
- ngsolve_webgpu/isosurface.py +101 -0
- ngsolve_webgpu/jupyter.py +168 -0
- ngsolve_webgpu/lic.py +115 -0
- ngsolve_webgpu/mesh.py +424 -0
- ngsolve_webgpu/shaders/clipping/common.wgsl +120 -0
- ngsolve_webgpu/shaders/clipping/compute.wgsl +30 -0
- ngsolve_webgpu/shaders/clipping/render.wgsl +37 -0
- ngsolve_webgpu/shaders/compute.wgsl +59 -0
- ngsolve_webgpu/shaders/elements3d.wgsl +83 -0
- ngsolve_webgpu/shaders/eval/common.wgsl +14 -0
- ngsolve_webgpu/shaders/eval/seg.wgsl +30 -0
- ngsolve_webgpu/shaders/eval/tet.wgsl +61 -0
- ngsolve_webgpu/shaders/eval/trig.wgsl +121 -0
- ngsolve_webgpu/shaders/eval.wgsl +0 -0
- ngsolve_webgpu/shaders/geo_edge.wgsl +55 -0
- ngsolve_webgpu/shaders/geo_face.wgsl +47 -0
- ngsolve_webgpu/shaders/geo_vertex.wgsl +71 -0
- ngsolve_webgpu/shaders/isosurface/compute.wgsl +63 -0
- ngsolve_webgpu/shaders/isosurface/negative_clipping.wgsl +13 -0
- ngsolve_webgpu/shaders/isosurface/negative_surface.wgsl +15 -0
- ngsolve_webgpu/shaders/isosurface/render.wgsl +50 -0
- ngsolve_webgpu/shaders/line_integral_convolution.wgsl +100 -0
- ngsolve_webgpu/shaders/mesh.wgsl +296 -0
- ngsolve_webgpu/shaders/numbers.wgsl +40 -0
- ngsolve_webgpu/shaders/shader.wgsl +111 -0
- ngsolve_webgpu/shaders/uniforms.wgsl +93 -0
- ngsolve_webgpu-0.0.1.dist-info/METADATA +13 -0
- ngsolve_webgpu-0.0.1.dist-info/RECORD +37 -0
- ngsolve_webgpu-0.0.1.dist-info/WHEEL +5 -0
- ngsolve_webgpu-0.0.1.dist-info/licenses/LICENSE +504 -0
- 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
|