Skip to content

REFACTOR: Add required graphics decorator #6087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/6087.miscellaneous.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add required graphics decorator
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ doc = [
"pyvista[io]>=0.38.0,<0.45",
"ansys-tools-visualization-interface; python_version >= '3.10'",
]
graphics = [
"ansys-tools-visualization-interface; python_version >= '3.10'",
"pyvista[io]>=0.38.0,<0.45",
"matplotlib>=3.5.0,<3.11",
"vtk>=9.0,<9.4",
]
jupyter = [
"jupyterlab>=3.6.0,<4.5",
"ipython>=7.30.0,<9.1",
Expand Down
6 changes: 4 additions & 2 deletions src/ansys/aedt/core/application/analysis_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from ansys.aedt.core.generic.file_utils import read_component_file
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
from ansys.aedt.core.generic.settings import settings
from ansys.aedt.core.internal.checks import graphics_required
from ansys.aedt.core.internal.checks import min_aedt_version


Expand Down Expand Up @@ -1134,9 +1135,9 @@ def flatten_3d_components(self, components=None, purge_history=True, password=No
return True

@pyaedt_function_handler(object_name="assignment")
@graphics_required
@min_aedt_version("2023.2")
def identify_touching_conductors(self, assignment=None):
# type: (str) -> dict
"""Identify all touching components and group in a dictionary.

This method requires that the ``pyvista`` package is installed.
Expand All @@ -1151,6 +1152,8 @@ def identify_touching_conductors(self, assignment=None):
dict

"""
import pyvista as pv

if self.design_type == "HFSS": # pragma: no cover
nets_aedt = self.oboundary.IdentifyNets(True)
nets = {}
Expand All @@ -1163,7 +1166,6 @@ def identify_touching_conductors(self, assignment=None):
return output
return nets
plt_obj = self.plot(assignment=self.get_all_conductors_names(), show=False)
import pyvista as pv

nets = {}
inputs = []
Expand Down
5 changes: 4 additions & 1 deletion src/ansys/aedt/core/extensions/project/points_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
from ansys.aedt.core.extensions.misc import get_port
from ansys.aedt.core.extensions.misc import get_process_id
from ansys.aedt.core.extensions.misc import is_student
from ansys.aedt.core.internal.checks import graphics_required
from ansys.aedt.core.visualization.plot.pyvista import ModelPlotter
import pyvista as pv

port = get_port()
version = get_aedt_version()
Expand Down Expand Up @@ -253,7 +253,10 @@ def callback():
master.output_file = output_file_entry.get("1.0", tkinter.END).strip()
master.destroy()

@graphics_required
def preview():
import pyvista as pv

try:
selected_objects = [objects_list_lb.get(i) for i in objects_list_lb.curselection()]
if not selected_objects or any(
Expand Down
71 changes: 71 additions & 0 deletions src/ansys/aedt/core/internal/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,28 @@

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

import os
import warnings

from ansys.aedt.core.internal.errors import AEDTRuntimeError


def install_message(target: str) -> str:
""""""
capitalized_target = target.capitalize()
return (
f"{capitalized_target} dependencies are required. Please install the"
f" ``{target}`` target to use this method. You can install it by running"
f" `pip install pyaedt[{target}]` or `pip install pyaedt[all]`."
)


ERROR_GRAPHICS_REQUIRED = install_message("graphics")
"""Message to display when graphics are required for a method."""
__GRAPHICS_AVAILABLE = None
"""Global variable to store the result of the graphics imports."""


def min_aedt_version(min_version: str):
"""Compare a minimum required version to the current AEDT version.

Expand Down Expand Up @@ -87,3 +106,55 @@ def wrapper(self, *args, **kwargs):
return wrapper

return aedt_version_decorator


def check_graphics_available(warning: bool = False):
"""Check if graphics are available."""
global __GRAPHICS_AVAILABLE

if __GRAPHICS_AVAILABLE is None:
try:
# isort: off
from ansys.tools.visualization_interface import Plotter # noqa: F401

# NOTE: Manually added imports due to our use of pyvista's io install target
# Using packaging might be a better solution to be dynamic.
import pyvista as pv # noqa: F401
import imageio # noqa: F401
import meshio # noqa: F401

import matplotlib # noqa: F401
import vtk # noqa: F401

# isort: on

_GRAPHICS_AVAILABLE = True
except ImportError: # pragma: no cover
_GRAPHICS_AVAILABLE = False

if _GRAPHICS_AVAILABLE is False: # pragma: no cover
if warning or "PYTEST_CURRENT_TEST" in os.environ:
warnings.warn(ERROR_GRAPHICS_REQUIRED)
else:
raise ImportError(ERROR_GRAPHICS_REQUIRED)


def graphics_required(method):
"""Decorate a method as requiring graphics.

Parameters
----------
method : callable
Method to decorate.

Returns
-------
callable
Decorated method.
"""

def aedt_graphics_decorator(*args, **kwargs):
check_graphics_available()
return method(*args, **kwargs)

return aedt_graphics_decorator
21 changes: 13 additions & 8 deletions src/ansys/aedt/core/modeler/advanced_cad/oms.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
from ansys.aedt.core.generic.general_methods import settings
from ansys.aedt.core.internal.checks import graphics_required

logger = settings.logger

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

try:
import pyvista as pv
import vtk
except ImportError: # pragma: no cover
warnings.warn(
"The PyVista module is required to use the OpenStreetMap Reader.\n" "Install with \n\npip install pyvista"
)

try:
import osmnx as ox

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

@staticmethod
@pyaedt_function_handler()
@graphics_required
def create_building_roof(all_pos):
"""Generate a filled in polygon from outline.

Expand All @@ -76,6 +70,9 @@ def create_building_roof(all_pos):
-------
:class:`pyvista.PolygonData`
"""
import pyvista as pv
import vtk

points = vtk.vtkPoints()
for each in all_pos:
points.InsertNextPoint(each[0], each[1], each[2])
Expand Down Expand Up @@ -110,6 +107,7 @@ def create_building_roof(all_pos):
return roof

@pyaedt_function_handler()
@graphics_required
def generate_buildings(self, center_lat_lon, terrain_mesh, max_radius=500):
"""Generate the buildings stl file.

Expand All @@ -127,6 +125,8 @@ def generate_buildings(self, center_lat_lon, terrain_mesh, max_radius=500):
dict
Info of generated stl file.
"""
import pyvista as pv

# TODO: Remove compatibility with <2.0 when releasing pyaedt v1.0 ?
try:
gdf = ox.geometries.geometries_from_point(center_lat_lon, tags={"building": True}, dist=max_radius)
Expand Down Expand Up @@ -264,6 +264,7 @@ def __init__(self, cad_path):
self.cad_path = cad_path

@pyaedt_function_handler()
@graphics_required
def create_roads(self, center_lat_lon, terrain_mesh, max_radius=1000, z_offset=0, road_step=10, road_width=5):
"""Generate the road stl file.

Expand All @@ -287,6 +288,8 @@ def create_roads(self, center_lat_lon, terrain_mesh, max_radius=1000, z_offset=0
dict
Info of generated stl file.
"""
import pyvista as pv

# TODO: Remove compatibility with <2.0 when releasing pyaedt v1.0 ?
try:
graph = ox.graph_from_point(
Expand Down Expand Up @@ -383,6 +386,7 @@ def __init__(self, cad_path="./"):
self.cad_path = cad_path

@pyaedt_function_handler()
@graphics_required
def get_terrain(self, center_lat_lon, max_radius=500, grid_size=5, buffer_percent=0):
"""Generate the terrain stl file.

Expand All @@ -403,6 +407,7 @@ def get_terrain(self, center_lat_lon, max_radius=500, grid_size=5, buffer_percen
dict
Info of generated stl file.
"""
import pyvista as pv

utm_center = convert_latlon_to_utm(center_lat_lon[0], center_lat_lon[1])
logger.info("Generating Terrain")
Expand Down
6 changes: 5 additions & 1 deletion src/ansys/aedt/core/modeler/modeler_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from ansys.aedt.core.generic.file_utils import generate_unique_name
from ansys.aedt.core.generic.file_utils import open_file
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
from ansys.aedt.core.internal.checks import ERROR_GRAPHICS_REQUIRED
from ansys.aedt.core.internal.errors import GrpcApiError
from ansys.aedt.core.modeler.cad.primitives_3d import Primitives3D
from ansys.aedt.core.modeler.geometry_operators import GeometryOperators
Expand Down Expand Up @@ -1309,7 +1310,10 @@ def import_from_openstreet_map(

self.logger.info("Done...")
if plot_before_importing:
import pyvista as pv
try:
import pyvista as pv
except ImportError:
raise ImportError(ERROR_GRAPHICS_REQUIRED)

self.logger.info("Viewing Geometry...")
# view results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from ansys.aedt.core.generic.general_methods import conversion_function
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
from ansys.aedt.core.generic.numbers import decompose_variable_value
from ansys.aedt.core.internal.checks import graphics_required
from ansys.aedt.core.visualization.advanced.touchstone_parser import read_touchstone
from ansys.aedt.core.visualization.plot.matplotlib import ReportPlotter
from ansys.aedt.core.visualization.plot.matplotlib import is_notebook
Expand All @@ -53,14 +54,6 @@
)
np = None

try:
import pyvista as pv
except ImportError: # pragma: no cover
warnings.warn(
"The PyVista module is required to run functionalities of FfdSolutionData.\n"
"Install with \n\npip install pyvista"
)
pv = None

defusedxml.defuse_stdlib()

Expand Down Expand Up @@ -1089,6 +1082,7 @@ def plot_3d_chart(
return new

@pyaedt_function_handler()
@graphics_required
def plot_3d(
self,
quantity="RealizedGain",
Expand Down Expand Up @@ -1153,6 +1147,8 @@ def plot_3d(
>>> data = app.get_antenna_data(setup=setup_name,sphere=sphere)
>>> data.plot_3d(quantity_format="dB10")
"""
import pyvista as pv

if not rotation:
rotation = np.eye(3)
elif isinstance(rotation, (list, tuple)): # pragma: no cover
Expand Down
21 changes: 12 additions & 9 deletions src/ansys/aedt/core/visualization/advanced/hdm_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

from ansys.aedt.core.generic.constants import AEDT_UNITS
from ansys.aedt.core.generic.general_methods import pyaedt_function_handler
from ansys.aedt.core.internal.checks import graphics_required
from ansys.aedt.core.visualization.plot.pyvista import CommonPlotter
from ansys.aedt.core.visualization.plot.pyvista import ObjClass

Expand All @@ -39,15 +40,6 @@
"The NumPy module is required to run some functionalities of PostProcess.\n"
"Install with \n\npip install numpy"
)
try:
import pyvista as pv

pyvista_available = True
except ImportError:
warnings.warn(
"The PyVista module is required to run some functionalities of PostProcess.\n"
"Install with \n\npip install pyvista\n\nRequires CPython."
)


class HDMPlotter(CommonPlotter):
Expand Down Expand Up @@ -136,6 +128,7 @@ def add_ray_helper(depth, next_bounce):
return points, lines, depths

@pyaedt_function_handler()
@graphics_required
def plot_rays(self, snapshot_path=None):
"""Plot Rays read from an ``hdm`` file.

Expand All @@ -154,7 +147,10 @@ def plot_rays(self, snapshot_path=None):
This method is intended to be an example of the usage that can be made of hdm file.

"""
import pyvista as pv

warnings.warn("This method is intended to be an example of the usage that can be made of hdm file.")

if snapshot_path:
self.pv = pv.Plotter(notebook=self.is_notebook, off_screen=True, window_size=self.windows_size)
else:
Expand Down Expand Up @@ -199,6 +195,7 @@ def _first_bounce_currents(self):
return bounces

@pyaedt_function_handler()
@graphics_required
def plot_first_bounce_currents(self, snapshot_path=None):
"""Plot First bounce of currents read from an ``hdm`` file.

Expand All @@ -211,7 +208,10 @@ def plot_first_bounce_currents(self, snapshot_path=None):
-------
:class:`pyvista.Plotter`
"""
import pyvista as pv

warnings.warn("This method is intended to be an example of the usage that can be made of hdm file.")

currents = self._first_bounce_currents()
points = []
faces = []
Expand Down Expand Up @@ -242,7 +242,10 @@ def plot_first_bounce_currents(self, snapshot_path=None):
self.pv.show(auto_close=False)

@pyaedt_function_handler()
@graphics_required
def _add_objects(self):
import pyvista as pv

if self._objects:
for cad in self._objects:
if not cad._cached_polydata:
Expand Down
Loading
Loading