Skip to content

Commit f4bb518

Browse files
committed
fix more interactions
1 parent e9a30a2 commit f4bb518

7 files changed

Lines changed: 184 additions & 28 deletions

File tree

ngsolve_webgpu/cf.py

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -622,15 +622,18 @@ def component(self, value):
622622
self.uniform.update_buffer()
623623

624624

625-
def _complex_phase_export_interactions(uniforms_with_mode, registry, label="Complex Animation"):
626-
"""Build a generic gui ExportInteraction for complex phase animation.
625+
def _complex_phase_export_interactions(uniforms_with_mode, registry, label="Complex"):
626+
"""Build a generic gui ExportInteraction for complex visualization.
627627
628628
uniforms_with_mode: iterable of (uniform_obj, has_mode_field).
629-
has_mode_field=True for ComplexUniform (mode at offset 0, phase at offset 4),
630-
False for ShapeComplexUniform (phase at offset 0, no mode).
629+
has_mode_field=True for ComplexUniform (mode @ offset 0, phase @ 4),
630+
False for ShapeComplexUniform (phase @ 0, no mode).
631+
632+
Emits: Mode dropdown (if any mode-carrying uniform), Phase slider,
633+
Animate checkbox, Speed slider.
631634
"""
632635
from webgpu.export.gui import (
633-
gui_interaction, Checkbox, Slider, Write, Target,
636+
gui_interaction, Checkbox, Slider, Dropdown, Write, Target,
634637
)
635638

636639
phase_targets = []
@@ -651,28 +654,99 @@ def _complex_phase_export_interactions(uniforms_with_mode, registry, label="Comp
651654
if not phase_targets:
652655
return []
653656

654-
controls = [
655-
Checkbox(var="animate", name="Animate", default=False),
656-
Slider(var="speed", name="Speed", default=1.0, min=0.1, max=5.0, step=0.1),
657-
]
658-
writes = [
659-
Write(
660-
targets=phase_targets,
661-
expr="(t*speed*2*Math.PI) % (2*Math.PI)",
662-
when="animate",
663-
),
664-
]
657+
controls = []
658+
writes = []
665659
if mode_targets:
666-
# Force PHASE_ROTATE mode whenever animation is toggled on.
660+
# 0=Real (PHASE_ROTATE,phase=0), 1=Imag (PHASE_ROTATE,phase=-pi/2),
661+
# 2=Abs (mode=ABS), 3=Arg (mode=ARG).
662+
controls.append(Dropdown(
663+
var="cmode", name="Mode",
664+
options={"Real": 0, "Imag": 1, "Abs": 2, "Arg": 3},
665+
default=0,
666+
))
667+
# Map cmode -> shader mode: 0,1 -> 0; 2 -> 1; 3 -> 2.
667668
writes.append(Write(
668669
targets=mode_targets,
669-
value=0,
670-
when="animate",
671-
trigger="animate",
672-
))
670+
expr="cmode <= 1 ? 0 : (cmode - 1)",
671+
trigger="cmode",
672+
))
673+
# Real/Imag set fixed phase (only when not animating).
674+
writes.append(Write(
675+
targets=phase_targets,
676+
expr="cmode == 1 ? -Math.PI/2 : 0",
677+
when="cmode <= 1 && !animate",
678+
trigger="cmode",
679+
))
680+
681+
controls.append(Slider(var="phase", name="Phase",
682+
default=0.0, min=0.0, max=6.283, step=0.01))
683+
writes.append(Write(
684+
targets=phase_targets, expr="phase",
685+
when="!animate", trigger="phase",
686+
))
687+
688+
controls.append(Checkbox(var="animate", name="Animate", default=False))
689+
controls.append(Slider(var="speed", name="Speed",
690+
default=1.0, min=0.1, max=5.0, step=0.1))
691+
writes.append(Write(
692+
targets=phase_targets,
693+
expr="(t*speed*2*Math.PI) % (2*Math.PI)",
694+
when="animate",
695+
))
696+
if mode_targets:
697+
# Force PHASE_ROTATE mode while animating.
698+
writes.append(Write(
699+
targets=mode_targets, value=0,
700+
when="animate", trigger="animate",
701+
))
673702
return [gui_interaction(label, controls, writes)]
674703

675704

705+
def _component_export_interactions(uniform, dim, registry, label="Function"):
706+
"""Dropdown to select the active component (i32 at offset 0)."""
707+
from webgpu.export.gui import gui_interaction, Dropdown, Write, Target
708+
709+
if uniform is None or dim <= 1:
710+
return []
711+
buf = getattr(uniform, "_buffer", None)
712+
if buf is None:
713+
return []
714+
key = id(buf)
715+
if key not in registry._buffers:
716+
return []
717+
buf_id = registry._buffers[key][0]
718+
options = {"Norm": -1}
719+
for d in range(dim):
720+
options[str(d)] = d
721+
return [gui_interaction(
722+
label,
723+
[Dropdown(var="component", name="Component", options=options, default=-1)],
724+
[Write(targets=[Target(buf_id, offset=0, dtype="i32")],
725+
expr="component", trigger="component")],
726+
)]
727+
728+
729+
def _shrink_export_interactions(uniform, registry, label="3D Elements", offset=4):
730+
"""Slider for the shrink field (f32) of a uniform buffer."""
731+
from webgpu.export.gui import gui_interaction, Slider, Write, Target
732+
733+
if uniform is None:
734+
return []
735+
buf = getattr(uniform, "_buffer", None)
736+
if buf is None:
737+
return []
738+
key = id(buf)
739+
if key not in registry._buffers:
740+
return []
741+
buf_id = registry._buffers[key][0]
742+
return [gui_interaction(
743+
label,
744+
[Slider(var="shrink", name="Shrink", default=1.0, min=0.0, max=1.0, step=0.01)],
745+
[Write(targets=[Target(buf_id, offset=offset, dtype="f32")],
746+
expr="shrink", trigger="shrink")],
747+
)]
748+
749+
676750
class ComplexUniform(UniformBase):
677751
_binding = Binding.COMPLEX_SETTINGS
678752
_fields_ = [
@@ -818,7 +892,10 @@ def on_component_change(self, callback):
818892
self._on_component_change.append(callback)
819893

820894
def get_bounding_box(self):
821-
return self.data.get_bounding_box()
895+
bbox = self.data.get_bounding_box()
896+
if self.symmetry:
897+
bbox = self.symmetry.expand_bbox(bbox)
898+
return bbox
822899

823900
def add_options_to_gui(self, gui):
824901
if gui is None:
@@ -914,6 +991,9 @@ def get_bindings(self):
914991

915992
def get_export_interactions(self, options, buffer_registry):
916993
out = list(super().get_export_interactions(options, buffer_registry))
994+
out += _component_export_interactions(
995+
self.gpu_objects.settings.uniform, self.data.cf.dim, buffer_registry,
996+
)
917997
if self.data.cf.is_complex:
918998
out += _complex_phase_export_interactions(
919999
[(self.gpu_objects.complex_settings.uniform, True)],

ngsolve_webgpu/clipping.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import numpy as np
1212

13-
from .cf import FunctionData, FunctionSettings, ComplexSettings, PhaseAnimation, _complex_phase_export_interactions
13+
from .cf import FunctionData, FunctionSettings, ComplexSettings, PhaseAnimation, _complex_phase_export_interactions, _component_export_interactions
1414
from .cf import Binding as CFBinding
1515

1616
from .mesh import MeshElements3d, ElType
@@ -123,7 +123,10 @@ def update(self, options: RenderOptions):
123123
self.shader_defines["SYMMETRY"] = "1"
124124

125125
def get_bounding_box(self):
126-
return self.data.get_bounding_box()
126+
bbox = self.data.get_bounding_box()
127+
if self.symmetry:
128+
bbox = self.symmetry.expand_bbox(bbox)
129+
return bbox
127130

128131
def get_shader_code(self):
129132
return read_shader_file("ngsolve/clipping/render.wgsl")
@@ -190,12 +193,25 @@ def _set_speed_from_gui(self, value):
190193
self._phase_animation.speed = value
191194

192195
def get_export_interactions(self, options, buffer_registry):
196+
from webgpu.export.gui import gui_interaction, Checkbox, Write, Target
193197
out = list(super().get_export_interactions(options, buffer_registry))
198+
out += _component_export_interactions(
199+
self.gpu_objects.settings.uniform, self.data.cf.dim, buffer_registry,
200+
)
194201
if self.data.cf.is_complex:
195202
out += _complex_phase_export_interactions(
196203
[(self.gpu_objects.complex_settings.uniform, True)],
197204
buffer_registry,
198205
)
206+
# Toggle the clipping function render pass on/off.
207+
out.append(gui_interaction(
208+
"Clipping Function",
209+
[Checkbox(var="enabled", name="Enabled", default=True)],
210+
[Write(
211+
targets=[Target(f"render_{self._id}", dtype="pass_enable")],
212+
expr="enabled ? 1 : 0", trigger="enabled",
213+
)],
214+
))
199215
return out
200216

201217
def add_options_to_gui(self, gui):

ngsolve_webgpu/facet_cf.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,16 @@ def get_bindings(self):
341341
*self.gpu_objects.settings.get_bindings(),
342342
*self.gpu_objects.complex_settings.get_bindings(),
343343
]
344+
345+
def get_export_interactions(self, options, buffer_registry):
346+
from .cf import _complex_phase_export_interactions, _component_export_interactions
347+
out = list(super().get_export_interactions(options, buffer_registry))
348+
out += _component_export_interactions(
349+
self.gpu_objects.settings.uniform, self.cf.dim, buffer_registry,
350+
)
351+
if self.cf.is_complex:
352+
out += _complex_phase_export_interactions(
353+
[(self.gpu_objects.complex_settings.uniform, True)],
354+
buffer_registry,
355+
)
356+
return out

ngsolve_webgpu/geometry.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ def __init__(self, geo, clipping, symmetry=None):
6262
self._buffers = {}
6363

6464
def get_bounding_box(self):
65-
return self.bounding_box
65+
bbox = self.bounding_box
66+
if self.symmetry:
67+
bbox = self.symmetry.expand_bbox(bbox)
68+
return bbox
6669

6770
def set_colors(self, colors):
6871
"""colors is numpy float32 array with 4 times number indices entries"""

ngsolve_webgpu/mesh.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,10 @@ def update(self, options: RenderOptions):
713713
self.shader_defines["SYMMETRY"] = "1"
714714

715715
def get_bounding_box(self):
716-
return self.data.get_bounding_box()
716+
bbox = self.data.get_bounding_box()
717+
if self.symmetry:
718+
bbox = self.symmetry.expand_bbox(bbox)
719+
return bbox
717720

718721
def get_bindings(self):
719722
from .cf import FunctionData
@@ -956,7 +959,10 @@ def shrink(self, value):
956959
self.uniforms.update_buffer()
957960

958961
def get_bounding_box(self) -> tuple[list[float], list[float]] | None:
959-
return self.data.get_bounding_box()
962+
bbox = self.data.get_bounding_box()
963+
if self.symmetry:
964+
bbox = self.symmetry.expand_bbox(bbox)
965+
return bbox
960966

961967
def update(self, options: RenderOptions):
962968
if self.uniforms is None:
@@ -1013,6 +1019,12 @@ def set_shrink(value):
10131019
label="Shrink", value=self.uniforms.shrink, min=0.0, max=1.0, step=0.01, func=set_shrink
10141020
)
10151021

1022+
def get_export_interactions(self, options, buffer_registry):
1023+
from .cf import _shrink_export_interactions
1024+
out = list(super().get_export_interactions(options, buffer_registry))
1025+
out += _shrink_export_interactions(self.uniforms, buffer_registry)
1026+
return out
1027+
10161028
def get_bindings(self):
10171029
bindings = [
10181030
*self.clipping.get_bindings(),

ngsolve_webgpu/symmetry.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,35 @@ def n_copies(self):
7272
"""Number of symmetry copies (including original)."""
7373
return len(self._get_transforms())
7474

75+
def expand_bbox(self, bbox):
76+
"""Return a bbox enclosing all symmetry copies of the input bbox.
77+
78+
Applies every transform matrix to the 8 corners of ``bbox`` and takes
79+
the union. Returns ``None`` if ``bbox`` is ``None``.
80+
"""
81+
if bbox is None:
82+
return None
83+
pmin = np.array(bbox[0], dtype=float)
84+
pmax = np.array(bbox[1], dtype=float)
85+
corners = np.array([
86+
[pmin[0], pmin[1], pmin[2], 1.0],
87+
[pmax[0], pmin[1], pmin[2], 1.0],
88+
[pmin[0], pmax[1], pmin[2], 1.0],
89+
[pmax[0], pmax[1], pmin[2], 1.0],
90+
[pmin[0], pmin[1], pmax[2], 1.0],
91+
[pmax[0], pmin[1], pmax[2], 1.0],
92+
[pmin[0], pmax[1], pmax[2], 1.0],
93+
[pmax[0], pmax[1], pmax[2], 1.0],
94+
])
95+
new_min = pmin.copy()
96+
new_max = pmax.copy()
97+
for mat, _, _ in self._get_transforms():
98+
transformed = corners @ mat.T
99+
pts = transformed[:, :3]
100+
new_min = np.minimum(new_min, pts.min(axis=0))
101+
new_max = np.maximum(new_max, pts.max(axis=0))
102+
return ([float(x) for x in new_min], [float(x) for x in new_max])
103+
75104
def _add_generator(self, matrix):
76105
self._generators.append(matrix)
77106
self._transforms = None # invalidate cache

ngsolve_webgpu/vectors.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ def set_grid_size(self, grid_size: float):
9797
)
9898

9999
def get_bounding_box(self):
100-
return self.function_data.mesh_data.get_bounding_box()
100+
bbox = self.function_data.mesh_data.get_bounding_box()
101+
if self.symmetry:
102+
bbox = self.symmetry.expand_bbox(bbox)
103+
return bbox
101104

102105
def generate_shape(self):
103106
cyl = generate_cylinder(8, 0.05, 0.5, bottom_face=True)

0 commit comments

Comments
 (0)