Skip to content

Commit 69662a0

Browse files
committed
facet renderer, colormap widens instead of sets
scenes with multiple renderers change in tests because colormap now widens instead of sets on autoscale
1 parent 94c51f5 commit 69662a0

26 files changed

Lines changed: 936 additions & 26 deletions

docs/element_boundaries.ipynb

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "a1",
6+
"metadata": {},
7+
"source": [
8+
"# Element Boundaries\n",
9+
"\n",
10+
"Visualize CoefficientFunctions evaluated on element boundaries (facets).\n",
11+
"Each element face is rendered independently, so inter-element jumps become visible —\n",
12+
"useful for DG/HDG methods, L2 spaces, and FacetFESpace functions.\n",
13+
"\n",
14+
"- **2D**: `FacetFunctionData` + `FacetCFRenderer` draw colored thick lines on element edges.\n",
15+
"- **3D**: `FacetCFRenderer3D` renders all tet faces with slight shrink, using per-face Bernstein evaluation."
16+
]
17+
},
18+
{
19+
"cell_type": "markdown",
20+
"id": "a2",
21+
"metadata": {},
22+
"source": [
23+
"## 2D: Continuous Function\n",
24+
"\n",
25+
"Even for a smooth CF, element boundary rendering shows the value along every edge."
26+
]
27+
},
28+
{
29+
"cell_type": "code",
30+
"execution_count": null,
31+
"id": "a3",
32+
"metadata": {},
33+
"outputs": [],
34+
"source": [
35+
"from ngsolve import *\n",
36+
"from ngsolve_webgpu import *\n",
37+
"from webgpu.jupyter import Draw\n",
38+
"\n",
39+
"mesh = Mesh(unit_square.GenerateMesh(maxh=0.15))\n",
40+
"mdata = MeshData(mesh)\n",
41+
"\n",
42+
"facet_data = FacetFunctionData(mdata, sin(5 * x) * y, order=3)\n",
43+
"colormap = Colormap(minval=-1, maxval=1)\n",
44+
"renderer = FacetCFRenderer(facet_data, colormap=colormap)\n",
45+
"wireframe = MeshWireframe2d(mdata)\n",
46+
"\n",
47+
"Draw([renderer, wireframe, Colorbar(colormap)])"
48+
]
49+
},
50+
{
51+
"cell_type": "markdown",
52+
"id": "a4",
53+
"metadata": {},
54+
"source": [
55+
"## 2D: L2 Function — Inter-Element Jumps\n",
56+
"\n",
57+
"For discontinuous (L2) functions, each edge is rendered twice (once per adjacent element),\n",
58+
"naturally revealing jumps."
59+
]
60+
},
61+
{
62+
"cell_type": "code",
63+
"execution_count": null,
64+
"id": "a5",
65+
"metadata": {},
66+
"outputs": [],
67+
"source": [
68+
"mesh = Mesh(unit_square.GenerateMesh(maxh=0.2))\n",
69+
"mdata = MeshData(mesh)\n",
70+
"\n",
71+
"fes = L2(mesh, order=0)\n",
72+
"gf = GridFunction(fes)\n",
73+
"gf.vec.FV().NumPy()[:] = [i/len(gf.vec) for i in range(len(gf.vec))]\n",
74+
"\n",
75+
"facet_data = FacetFunctionData(mdata, gf, order=2)\n",
76+
"colormap = Colormap()\n",
77+
"renderer = FacetCFRenderer(facet_data, colormap=colormap)\n",
78+
"wireframe = MeshWireframe2d(mdata)\n",
79+
"\n",
80+
"Draw([renderer, wireframe, Colorbar(colormap)])"
81+
]
82+
},
83+
{
84+
"cell_type": "markdown",
85+
"id": "a6",
86+
"metadata": {},
87+
"source": [
88+
"## 3D: Continuous Function on a Tet Mesh\n",
89+
"\n",
90+
"`FacetCFRenderer3D` renders all faces of all 3D elements with a slight shrink\n",
91+
"so adjacent element faces separate, revealing the function on each face.\n",
92+
"Clipping cuts through individual faces (not whole elements)."
93+
]
94+
},
95+
{
96+
"cell_type": "code",
97+
"execution_count": null,
98+
"id": "a7",
99+
"metadata": {},
100+
"outputs": [],
101+
"source": [
102+
"from ngsolve_webgpu.facet_cf import FacetCFRenderer3D\n",
103+
"from webgpu.clipping import Clipping\n",
104+
"\n",
105+
"mesh = Mesh(unit_cube.GenerateMesh(maxh=0.3))\n",
106+
"mdata = MeshData(mesh)\n",
107+
"\n",
108+
"colormap = Colormap()\n",
109+
"clipping = Clipping()\n",
110+
"clipping.mode = clipping.Mode.PLANE\n",
111+
"clipping.center = [0.5, 0.5, 0.5]\n",
112+
"clipping.normal = [1, 0, 0]\n",
113+
"\n",
114+
"renderer = FacetCFRenderer3D(mdata, x + 2 * y, order=2,\n",
115+
" colormap=colormap, clipping=clipping)\n",
116+
"\n",
117+
"scene = Draw([renderer, Colorbar(colormap)])\n",
118+
"clipping.add_options_to_gui(scene.gui)"
119+
]
120+
},
121+
{
122+
"cell_type": "markdown",
123+
"id": "a8",
124+
"metadata": {},
125+
"source": [
126+
"## 3D: FacetFESpace — HDG Example\n",
127+
"\n",
128+
"FacetFESpace functions live on element interfaces and cannot be evaluated inside elements.\n",
129+
"Element boundary rendering handles this naturally via `MapToAllElements` with `element_boundary=BND`."
130+
]
131+
},
132+
{
133+
"cell_type": "code",
134+
"execution_count": null,
135+
"id": "a9",
136+
"metadata": {},
137+
"outputs": [],
138+
"source": [
139+
"mesh = Mesh(unit_cube.GenerateMesh(maxh=0.3))\n",
140+
"mdata = MeshData(mesh)\n",
141+
"\n",
142+
"order = 2\n",
143+
"V = L2(mesh, order=order)\n",
144+
"F = FacetFESpace(mesh, order=order, dirichlet=\".*\")\n",
145+
"fes = V * F\n",
146+
"\n",
147+
"u, uhat = fes.TrialFunction()\n",
148+
"v, vhat = fes.TestFunction()\n",
149+
"n = specialcf.normal(3)\n",
150+
"h = specialcf.mesh_size\n",
151+
"alpha = 4 * order ** 2 / h\n",
152+
"\n",
153+
"a = BilinearForm(fes, condense=True)\n",
154+
"a += grad(u) * grad(v) * dx\n",
155+
"a += (-n * grad(u) * (v - vhat) - n * grad(v) * (u - uhat)\n",
156+
" + alpha * (u - uhat) * (v - vhat)) * dx(element_boundary=True)\n",
157+
"\n",
158+
"f = LinearForm(fes)\n",
159+
"f += 1 * v * dx\n",
160+
"\n",
161+
"gf = GridFunction(fes)\n",
162+
"with TaskManager():\n",
163+
" a.Assemble()\n",
164+
" f.Assemble()\n",
165+
" solvers.BVP(bf=a, lf=f, gf=gf)\n",
166+
"\n",
167+
"# Render the facet component (uhat)\n",
168+
"colormap = Colormap()\n",
169+
"clipping = Clipping()\n",
170+
"clipping.mode = clipping.Mode.PLANE\n",
171+
"clipping.center = [0.5, 0.5, 0.5]\n",
172+
"clipping.normal = [1, 0, 0]\n",
173+
"\n",
174+
"renderer = FacetCFRenderer3D(mdata, gf.components[1], order=order,\n",
175+
" colormap=colormap, clipping=clipping)\n",
176+
"\n",
177+
"scene = Draw([renderer, Colorbar(colormap)])\n",
178+
"clipping.add_options_to_gui(scene.gui)"
179+
]
180+
},
181+
{
182+
"cell_type": "code",
183+
"execution_count": null,
184+
"id": "93442706-d076-492e-8c08-7ca04754b57a",
185+
"metadata": {},
186+
"outputs": [],
187+
"source": []
188+
}
189+
],
190+
"metadata": {
191+
"kernelspec": {
192+
"display_name": "Python 3 (ipykernel)",
193+
"language": "python",
194+
"name": "python3"
195+
},
196+
"language_info": {
197+
"codemirror_mode": {
198+
"name": "ipython",
199+
"version": 3
200+
},
201+
"file_extension": ".py",
202+
"mimetype": "text/x-python",
203+
"name": "python",
204+
"nbconvert_exporter": "python",
205+
"pygments_lexer": "ipython3",
206+
"version": "3.14.4"
207+
}
208+
},
209+
"nbformat": 4,
210+
"nbformat_minor": 5
211+
}

ngsolve_webgpu/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .mesh import MeshData, MeshWireframe2d, MeshElements2d, MeshElements3d, MeshSegments
22
from .entity_numbers import EntityNumbers, PointNumbers
33
from .cf import FunctionData, CFRenderer
4+
from .facet_cf import FacetFunctionData, FacetCFRenderer, FacetCFRenderer3D
45
from .pick import MeshPickResult, HighlightUniforms, GeoPickResult
56
from .clipping import ClippingCF
67
from webgpu.colormap import Colorbar, Colormap

ngsolve_webgpu/cf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -756,10 +756,10 @@ def update(self, options: RenderOptions):
756756
return
757757
super().update(options)
758758
if self.gpu_objects.colormap.autoscale:
759-
self.gpu_objects.colormap.set_min_max(
759+
self.gpu_objects.colormap.widen_range(
760760
self.data.minval[self.component + 1],
761761
self.data.maxval[self.component + 1],
762-
set_autoscale=False,
762+
timestamp=options.timestamp,
763763
)
764764
self.shader_defines["MAX_EVAL_ORDER"] = self.data.order
765765

ngsolve_webgpu/clipping.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ def update(self, options: RenderOptions):
111111
self._buffers = self.data.get_buffers()
112112
if self.gpu_objects.colormap.autoscale:
113113
component = self.gpu_objects.settings.component
114-
self.gpu_objects.colormap.set_min_max(
114+
self.gpu_objects.colormap.widen_range(
115115
self.data.minval[component + 1],
116116
self.data.maxval[component + 1],
117-
set_autoscale=False,
117+
timestamp=options.timestamp,
118118
)
119119
self.gpu_objects.complex_settings.update(options)
120120
self.build_clip_plane()

0 commit comments

Comments
 (0)