|
3 | 3 | # Copyright (c) CERN, 2024. # |
4 | 4 | # ########################################### # |
5 | 5 |
|
| 6 | +import pyvista as pv |
6 | 7 | import numpy as np |
7 | 8 | import matplotlib.pyplot as plt |
8 | 9 |
|
@@ -63,11 +64,9 @@ def plot3D(self, field='E', component='z', clim=None, hide_solids=None, |
63 | 64 | Timestep number to be added to the plot title and figsave title. |
64 | 65 | ''' |
65 | 66 | if self.use_mpi: |
66 | | - print('*** plot3D is not supported when `use_mpi=True`') |
| 67 | + print('[!] plot3D is not supported when `use_mpi=True`') |
67 | 68 | return |
68 | 69 |
|
69 | | - import pyvista as pv |
70 | | - |
71 | 70 | if len(field) == 2: #support for e.g. field='Ex' |
72 | 71 | component = field[1] |
73 | 72 | field = field[0] |
@@ -262,11 +261,9 @@ def plot3DonSTL(self, field='E', component='z', clim=None, cmap='jet', log_scale |
262 | 261 | https://docs.pyvista.org/api/plotting/_autosummary/pyvista.plotter.add_mesh |
263 | 262 | ''' |
264 | 263 | if self.use_mpi: |
265 | | - print('*** plot3D is not supported when `use_mpi=True`') |
| 264 | + print('[!] plot3D is not supported when `use_mpi=True`') |
266 | 265 | return |
267 | 266 |
|
268 | | - import pyvista as pv |
269 | | - |
270 | 267 | if len(field) == 2: #support for e.g. field='Ex' |
271 | 268 | component = field[1] |
272 | 269 | field = field[0] |
@@ -821,8 +818,145 @@ def plot1D(self, field='E', component='z', line='z', pos=[0.5], |
821 | 818 | else: |
822 | 819 | plt.show() |
823 | 820 |
|
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() |
826 | 960 |
|
827 | 961 | def _add_logo_widget(self, pl): |
828 | 962 | """Add packaged logo via importlib.resources (Python 3.9+).""" |
|
0 commit comments