Skip to content

Commit 6b5e9d0

Browse files
committed
feature: add method inspect() to interactively show all the solids imported, with the materials and colors from dicts + the beam and integration path if wake object is passed, that can be toggled via checkboxes. Other minor changes to follow Ruff suggestions
1 parent 5629281 commit 6b5e9d0

File tree

1 file changed

+142
-8
lines changed

1 file changed

+142
-8
lines changed

wakis/plotting.py

Lines changed: 142 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Copyright (c) CERN, 2024. #
44
# ########################################### #
55

6+
import pyvista as pv
67
import numpy as np
78
import matplotlib.pyplot as plt
89

@@ -63,11 +64,9 @@ def plot3D(self, field='E', component='z', clim=None, hide_solids=None,
6364
Timestep number to be added to the plot title and figsave title.
6465
'''
6566
if self.use_mpi:
66-
print('*** plot3D is not supported when `use_mpi=True`')
67+
print('[!] plot3D is not supported when `use_mpi=True`')
6768
return
6869

69-
import pyvista as pv
70-
7170
if len(field) == 2: #support for e.g. field='Ex'
7271
component = field[1]
7372
field = field[0]
@@ -262,11 +261,9 @@ def plot3DonSTL(self, field='E', component='z', clim=None, cmap='jet', log_scale
262261
https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter.add_mesh
263262
'''
264263
if self.use_mpi:
265-
print('*** plot3D is not supported when `use_mpi=True`')
264+
print('[!] plot3D is not supported when `use_mpi=True`')
266265
return
267266

268-
import pyvista as pv
269-
270267
if len(field) == 2: #support for e.g. field='Ex'
271268
component = field[1]
272269
field = field[0]
@@ -821,8 +818,145 @@ def plot1D(self, field='E', component='z', line='z', pos=[0.5],
821818
else:
822819
plt.show()
823820

824-
def inspect(self):
825-
pass
821+
def inspect(self, wake=None, window_size=None, off_screen=False,
822+
opacity=1.0, inactive_opacity=0.1, add_silhouette=False,
823+
specular=0.5, smooth_shading=False):
824+
825+
if self.use_mpi:
826+
print('[!] plot3D is not supported when `use_mpi=True`')
827+
return
828+
if wake is not None:
829+
self.solver.wake = wake
830+
831+
# Initialize plotter
832+
pl = pv.Plotter(window_size=window_size)
833+
solid_state = {}
834+
for key, path in self.stl_solids.items():
835+
surf = self.grid.read_stl(key)
836+
color = self.stl_colors.get(key, "lightgray")
837+
actor = pl.add_mesh(surf, color=color, name=key,
838+
opacity=inactive_opacity, silhouette=False,
839+
smooth_shading=smooth_shading, specular=specular)
840+
sil = None
841+
if add_silhouette:
842+
sil = pl.add_silhouette(surf, color="black", line_width=3.0)
843+
sil.SetVisibility(False)
844+
845+
solid_state[key] = {
846+
"actor": actor,
847+
"silhouette": sil,
848+
"active_opacity": opacity,
849+
"inactive_opacity": inactive_opacity,
850+
"checked": False,
851+
"highlight": False,
852+
"button": None,
853+
}
854+
855+
# UI scale and solid checkboxes positioning
856+
w, h = pl.window_size
857+
ui = h / 800.0
858+
box = max(16, int(20 * ui))
859+
font = max(8, int(12 * ui))
860+
pad = int(10 * ui)
861+
dy = box + pad
862+
cx = int(10 * ui)
863+
cy = h // 2
864+
865+
# checkboxes callbacks for solids and master (all On/Off)
866+
color_on = 'green'
867+
color_off = 'white'
868+
def apply(state):
869+
if state["highlight"]:
870+
state["actor"].GetProperty().SetOpacity(state["active_opacity"])
871+
if add_silhouette:
872+
state["silhouette"].SetVisibility(True)
873+
else:
874+
state["actor"].GetProperty().SetOpacity(state["inactive_opacity"])
875+
if add_silhouette:
876+
state["silhouette"].SetVisibility(False)
877+
878+
def make_cb(name):
879+
def _cb(v):
880+
s = solid_state[name]
881+
s["checked"] = bool(v)
882+
s["highlight"] = s["checked"]
883+
apply(s)
884+
return _cb
885+
886+
master_on = True
887+
def master_cb(v):
888+
nonlocal master_on
889+
master_on = bool(v)
890+
for s in solid_state.values():
891+
s["checked"] = master_on
892+
s["highlight"] = master_on
893+
btn = s["button"]
894+
if btn:
895+
rep = btn.GetRepresentation()
896+
if hasattr(rep, "SetState"):
897+
rep.SetState(1 if master_on else 0)
898+
apply(s)
899+
900+
pl.add_checkbox_button_widget(master_cb, value=False,
901+
color_on=color_on, color_off=color_off,
902+
position=(cx, cy), size=box)
903+
pl.add_text("All on", position=(cx + box + pad, cy), font_size=font)
904+
905+
for i, name in enumerate(solid_state):
906+
y = cy - (i + 2) * dy
907+
_color_on = self.stl_colors.get(name, color_on)
908+
if _color_on == 'white' or _color_on == [1.0, 1.0, 1.0]:
909+
_color_on = 'gray'
910+
btn = pl.add_checkbox_button_widget(make_cb(name), value=False,
911+
color_on=_color_on,
912+
color_off=color_off,
913+
position=(cx, y), size=box)
914+
solid_state[name]["button"] = btn
915+
pl.add_text(name, position=(cx + box + pad, y), font_size=font)
916+
917+
# Add beam & integration path checkboxes if wake object is passed
918+
if self.wake is not None:
919+
z_center = 0.5 * (self.grid.zmin + self.grid.zmax)
920+
z_height = self.grid.zmax - self.grid.zmin
921+
radius = 0.005 * max(self.grid.xmax-self.grid.xmin, self.grid.ymax-self.grid.ymin)
922+
923+
beam = pv.Cylinder(center=(self.wake.xsource, self.wake.ysource, z_center),
924+
direction=(0, 0, 1), height=z_height, radius=radius*1.1)
925+
path = pv.Cylinder(center=(self.wake.xtest, self.wake.ytest, z_center),
926+
direction=(0, 0, 1), height=z_height, radius=radius)
927+
928+
beam_actor = pl.add_mesh(beam, color="orange", name="beam", opacity=1.0)
929+
beam_actor.SetVisibility(False)
930+
path_actor = pl.add_mesh(path, color="blue", name="integration_path", opacity=1.0)
931+
path_actor.SetVisibility(False)
932+
933+
bx = int(w - box - 200 * ui)
934+
to = box + pad
935+
936+
def beam_cb(v):
937+
beam_actor.SetVisibility(bool(v))
938+
def path_cb(v):
939+
path_actor.SetVisibility(bool(v))
940+
941+
pl.add_checkbox_button_widget(path_cb, value=False,
942+
color_off=color_off, color_on="blue",
943+
position=(bx, cy + dy), size=box)
944+
pl.add_text("Integration path", position=(bx + to, cy + dy), font_size=font)
945+
946+
pl.add_checkbox_button_widget(beam_cb, value=False,
947+
color_off=color_off, color_on="orange",
948+
position=(bx, cy), size=box)
949+
pl.add_text("Beam", position=(bx + to, cy), font_size=font)
950+
951+
pl.set_background('mistyrose', top='white')
952+
self._add_logo_widget(pl)
953+
pl.add_axes()
954+
955+
# Save
956+
if off_screen:
957+
return pl
958+
else:
959+
pl.show()
826960

827961
def _add_logo_widget(self, pl):
828962
"""Add packaged logo via importlib.resources (Python 3.9+)."""

0 commit comments

Comments
 (0)