Skip to content

Commit 94336cb

Browse files
REFACTOR!: Add required graphics decorator (#6087)
Co-authored-by: pyansys-ci-bot <[email protected]>
1 parent 1d493d7 commit 94336cb

File tree

19 files changed

+245
-97
lines changed

19 files changed

+245
-97
lines changed

doc/changelog.d/6087.miscellaneous.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add required graphics decorator

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ doc = [
9292
"pyvista[io]>=0.38.0,<0.45",
9393
"ansys-tools-visualization-interface; python_version >= '3.10'",
9494
]
95+
graphics = [
96+
"ansys-tools-visualization-interface; python_version >= '3.10'",
97+
"pyvista[io]>=0.38.0,<0.45",
98+
"matplotlib>=3.5.0,<3.11",
99+
"vtk>=9.0,<9.4",
100+
]
95101
jupyter = [
96102
"jupyterlab>=3.6.0,<4.5",
97103
"ipython>=7.30.0,<9.1",

src/ansys/aedt/core/application/analysis_3d.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from ansys.aedt.core.generic.file_utils import read_component_file
4040
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
4141
from ansys.aedt.core.generic.settings import settings
42+
from ansys.aedt.core.internal.checks import graphics_required
4243
from ansys.aedt.core.internal.checks import min_aedt_version
4344

4445

@@ -1134,9 +1135,9 @@ def flatten_3d_components(self, components=None, purge_history=True, password=No
11341135
return True
11351136

11361137
@pyaedt_function_handler(object_name="assignment")
1138+
@graphics_required
11371139
@min_aedt_version("2023.2")
11381140
def identify_touching_conductors(self, assignment=None):
1139-
# type: (str) -> dict
11401141
"""Identify all touching components and group in a dictionary.
11411142
11421143
This method requires that the ``pyvista`` package is installed.
@@ -1151,6 +1152,8 @@ def identify_touching_conductors(self, assignment=None):
11511152
dict
11521153
11531154
"""
1155+
import pyvista as pv
1156+
11541157
if self.design_type == "HFSS": # pragma: no cover
11551158
nets_aedt = self.oboundary.IdentifyNets(True)
11561159
nets = {}
@@ -1163,7 +1166,6 @@ def identify_touching_conductors(self, assignment=None):
11631166
return output
11641167
return nets
11651168
plt_obj = self.plot(assignment=self.get_all_conductors_names(), show=False)
1166-
import pyvista as pv
11671169

11681170
nets = {}
11691171
inputs = []

src/ansys/aedt/core/extensions/project/points_cloud.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
from ansys.aedt.core.extensions.misc import get_port
3535
from ansys.aedt.core.extensions.misc import get_process_id
3636
from ansys.aedt.core.extensions.misc import is_student
37+
from ansys.aedt.core.internal.checks import graphics_required
3738
from ansys.aedt.core.visualization.plot.pyvista import ModelPlotter
38-
import pyvista as pv
3939

4040
port = get_port()
4141
version = get_aedt_version()
@@ -253,7 +253,10 @@ def callback():
253253
master.output_file = output_file_entry.get("1.0", tkinter.END).strip()
254254
master.destroy()
255255

256+
@graphics_required
256257
def preview():
258+
import pyvista as pv
259+
257260
try:
258261
selected_objects = [objects_list_lb.get(i) for i in objects_list_lb.curselection()]
259262
if not selected_objects or any(

src/ansys/aedt/core/internal/checks.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,28 @@
2424

2525
"""Provides functions for performing common checks."""
2626

27+
import os
28+
import warnings
29+
2730
from ansys.aedt.core.internal.errors import AEDTRuntimeError
2831

2932

33+
def install_message(target: str) -> str:
34+
""""""
35+
capitalized_target = target.capitalize()
36+
return (
37+
f"{capitalized_target} dependencies are required. Please install the"
38+
f" ``{target}`` target to use this method. You can install it by running"
39+
f" `pip install pyaedt[{target}]` or `pip install pyaedt[all]`."
40+
)
41+
42+
43+
ERROR_GRAPHICS_REQUIRED = install_message("graphics")
44+
"""Message to display when graphics are required for a method."""
45+
__GRAPHICS_AVAILABLE = None
46+
"""Global variable to store the result of the graphics imports."""
47+
48+
3049
def min_aedt_version(min_version: str):
3150
"""Compare a minimum required version to the current AEDT version.
3251
@@ -87,3 +106,55 @@ def wrapper(self, *args, **kwargs):
87106
return wrapper
88107

89108
return aedt_version_decorator
109+
110+
111+
def check_graphics_available(warning: bool = False):
112+
"""Check if graphics are available."""
113+
global __GRAPHICS_AVAILABLE
114+
115+
if __GRAPHICS_AVAILABLE is None:
116+
try:
117+
# isort: off
118+
from ansys.tools.visualization_interface import Plotter # noqa: F401
119+
120+
# NOTE: Manually added imports due to our use of pyvista's io install target
121+
# Using packaging might be a better solution to be dynamic.
122+
import pyvista as pv # noqa: F401
123+
import imageio # noqa: F401
124+
import meshio # noqa: F401
125+
126+
import matplotlib # noqa: F401
127+
import vtk # noqa: F401
128+
129+
# isort: on
130+
131+
_GRAPHICS_AVAILABLE = True
132+
except ImportError: # pragma: no cover
133+
_GRAPHICS_AVAILABLE = False
134+
135+
if _GRAPHICS_AVAILABLE is False: # pragma: no cover
136+
if warning or "PYTEST_CURRENT_TEST" in os.environ:
137+
warnings.warn(ERROR_GRAPHICS_REQUIRED)
138+
else:
139+
raise ImportError(ERROR_GRAPHICS_REQUIRED)
140+
141+
142+
def graphics_required(method):
143+
"""Decorate a method as requiring graphics.
144+
145+
Parameters
146+
----------
147+
method : callable
148+
Method to decorate.
149+
150+
Returns
151+
-------
152+
callable
153+
Decorated method.
154+
"""
155+
156+
def aedt_graphics_decorator(*args, **kwargs):
157+
check_graphics_available()
158+
return method(*args, **kwargs)
159+
160+
return aedt_graphics_decorator

src/ansys/aedt/core/modeler/advanced_cad/oms.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
3030
from ansys.aedt.core.generic.general_methods import settings
31+
from ansys.aedt.core.internal.checks import graphics_required
3132

3233
logger = settings.logger
3334

@@ -38,14 +39,6 @@
3839
"The NumPy module is required to use the OpenStreetMap Reader.\n" "Install with \n\npip install numpy"
3940
)
4041

41-
try:
42-
import pyvista as pv
43-
import vtk
44-
except ImportError: # pragma: no cover
45-
warnings.warn(
46-
"The PyVista module is required to use the OpenStreetMap Reader.\n" "Install with \n\npip install pyvista"
47-
)
48-
4942
try:
5043
import osmnx as ox
5144

@@ -63,6 +56,7 @@ def __init__(self, cad_path):
6356

6457
@staticmethod
6558
@pyaedt_function_handler()
59+
@graphics_required
6660
def create_building_roof(all_pos):
6761
"""Generate a filled in polygon from outline.
6862
@@ -76,6 +70,9 @@ def create_building_roof(all_pos):
7670
-------
7771
:class:`pyvista.PolygonData`
7872
"""
73+
import pyvista as pv
74+
import vtk
75+
7976
points = vtk.vtkPoints()
8077
for each in all_pos:
8178
points.InsertNextPoint(each[0], each[1], each[2])
@@ -110,6 +107,7 @@ def create_building_roof(all_pos):
110107
return roof
111108

112109
@pyaedt_function_handler()
110+
@graphics_required
113111
def generate_buildings(self, center_lat_lon, terrain_mesh, max_radius=500):
114112
"""Generate the buildings stl file.
115113
@@ -127,6 +125,8 @@ def generate_buildings(self, center_lat_lon, terrain_mesh, max_radius=500):
127125
dict
128126
Info of generated stl file.
129127
"""
128+
import pyvista as pv
129+
130130
# TODO: Remove compatibility with <2.0 when releasing pyaedt v1.0 ?
131131
try:
132132
gdf = ox.geometries.geometries_from_point(center_lat_lon, tags={"building": True}, dist=max_radius)
@@ -264,6 +264,7 @@ def __init__(self, cad_path):
264264
self.cad_path = cad_path
265265

266266
@pyaedt_function_handler()
267+
@graphics_required
267268
def create_roads(self, center_lat_lon, terrain_mesh, max_radius=1000, z_offset=0, road_step=10, road_width=5):
268269
"""Generate the road stl file.
269270
@@ -287,6 +288,8 @@ def create_roads(self, center_lat_lon, terrain_mesh, max_radius=1000, z_offset=0
287288
dict
288289
Info of generated stl file.
289290
"""
291+
import pyvista as pv
292+
290293
# TODO: Remove compatibility with <2.0 when releasing pyaedt v1.0 ?
291294
try:
292295
graph = ox.graph_from_point(
@@ -383,6 +386,7 @@ def __init__(self, cad_path="./"):
383386
self.cad_path = cad_path
384387

385388
@pyaedt_function_handler()
389+
@graphics_required
386390
def get_terrain(self, center_lat_lon, max_radius=500, grid_size=5, buffer_percent=0):
387391
"""Generate the terrain stl file.
388392
@@ -403,6 +407,7 @@ def get_terrain(self, center_lat_lon, max_radius=500, grid_size=5, buffer_percen
403407
dict
404408
Info of generated stl file.
405409
"""
410+
import pyvista as pv
406411

407412
utm_center = convert_latlon_to_utm(center_lat_lon[0], center_lat_lon[1])
408413
logger.info("Generating Terrain")

src/ansys/aedt/core/modeler/modeler_3d.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from ansys.aedt.core.generic.file_utils import generate_unique_name
3333
from ansys.aedt.core.generic.file_utils import open_file
3434
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
35+
from ansys.aedt.core.internal.checks import ERROR_GRAPHICS_REQUIRED
3536
from ansys.aedt.core.internal.errors import GrpcApiError
3637
from ansys.aedt.core.modeler.cad.primitives_3d import Primitives3D
3738
from ansys.aedt.core.modeler.geometry_operators import GeometryOperators
@@ -1309,7 +1310,10 @@ def import_from_openstreet_map(
13091310

13101311
self.logger.info("Done...")
13111312
if plot_before_importing:
1312-
import pyvista as pv
1313+
try:
1314+
import pyvista as pv
1315+
except ImportError:
1316+
raise ImportError(ERROR_GRAPHICS_REQUIRED)
13131317

13141318
self.logger.info("Viewing Geometry...")
13151319
# view results

src/ansys/aedt/core/visualization/advanced/farfield_visualization.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from ansys.aedt.core.generic.general_methods import conversion_function
3737
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
3838
from ansys.aedt.core.generic.numbers import decompose_variable_value
39+
from ansys.aedt.core.internal.checks import graphics_required
3940
from ansys.aedt.core.visualization.advanced.touchstone_parser import read_touchstone
4041
from ansys.aedt.core.visualization.plot.matplotlib import ReportPlotter
4142
from ansys.aedt.core.visualization.plot.matplotlib import is_notebook
@@ -53,14 +54,6 @@
5354
)
5455
np = None
5556

56-
try:
57-
import pyvista as pv
58-
except ImportError: # pragma: no cover
59-
warnings.warn(
60-
"The PyVista module is required to run functionalities of FfdSolutionData.\n"
61-
"Install with \n\npip install pyvista"
62-
)
63-
pv = None
6457

6558
defusedxml.defuse_stdlib()
6659

@@ -1089,6 +1082,7 @@ def plot_3d_chart(
10891082
return new
10901083

10911084
@pyaedt_function_handler()
1085+
@graphics_required
10921086
def plot_3d(
10931087
self,
10941088
quantity="RealizedGain",
@@ -1153,6 +1147,8 @@ def plot_3d(
11531147
>>> data = app.get_antenna_data(setup=setup_name,sphere=sphere)
11541148
>>> data.plot_3d(quantity_format="dB10")
11551149
"""
1150+
import pyvista as pv
1151+
11561152
if not rotation:
11571153
rotation = np.eye(3)
11581154
elif isinstance(rotation, (list, tuple)): # pragma: no cover

src/ansys/aedt/core/visualization/advanced/hdm_plot.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from ansys.aedt.core.generic.constants import AEDT_UNITS
3131
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
32+
from ansys.aedt.core.internal.checks import graphics_required
3233
from ansys.aedt.core.visualization.plot.pyvista import CommonPlotter
3334
from ansys.aedt.core.visualization.plot.pyvista import ObjClass
3435

@@ -39,15 +40,6 @@
3940
"The NumPy module is required to run some functionalities of PostProcess.\n"
4041
"Install with \n\npip install numpy"
4142
)
42-
try:
43-
import pyvista as pv
44-
45-
pyvista_available = True
46-
except ImportError:
47-
warnings.warn(
48-
"The PyVista module is required to run some functionalities of PostProcess.\n"
49-
"Install with \n\npip install pyvista\n\nRequires CPython."
50-
)
5143

5244

5345
class HDMPlotter(CommonPlotter):
@@ -136,6 +128,7 @@ def add_ray_helper(depth, next_bounce):
136128
return points, lines, depths
137129

138130
@pyaedt_function_handler()
131+
@graphics_required
139132
def plot_rays(self, snapshot_path=None):
140133
"""Plot Rays read from an ``hdm`` file.
141134
@@ -154,7 +147,10 @@ def plot_rays(self, snapshot_path=None):
154147
This method is intended to be an example of the usage that can be made of hdm file.
155148
156149
"""
150+
import pyvista as pv
151+
157152
warnings.warn("This method is intended to be an example of the usage that can be made of hdm file.")
153+
158154
if snapshot_path:
159155
self.pv = pv.Plotter(notebook=self.is_notebook, off_screen=True, window_size=self.windows_size)
160156
else:
@@ -199,6 +195,7 @@ def _first_bounce_currents(self):
199195
return bounces
200196

201197
@pyaedt_function_handler()
198+
@graphics_required
202199
def plot_first_bounce_currents(self, snapshot_path=None):
203200
"""Plot First bounce of currents read from an ``hdm`` file.
204201
@@ -211,7 +208,10 @@ def plot_first_bounce_currents(self, snapshot_path=None):
211208
-------
212209
:class:`pyvista.Plotter`
213210
"""
211+
import pyvista as pv
212+
214213
warnings.warn("This method is intended to be an example of the usage that can be made of hdm file.")
214+
215215
currents = self._first_bounce_currents()
216216
points = []
217217
faces = []
@@ -242,7 +242,10 @@ def plot_first_bounce_currents(self, snapshot_path=None):
242242
self.pv.show(auto_close=False)
243243

244244
@pyaedt_function_handler()
245+
@graphics_required
245246
def _add_objects(self):
247+
import pyvista as pv
248+
246249
if self._objects:
247250
for cad in self._objects:
248251
if not cad._cached_polydata:

0 commit comments

Comments
 (0)