From 685b8a921d04ae6f62b93e4345ff213fb9fd4d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Morais?= <146729917+SMoraisAnsys@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:33:24 +0100 Subject: [PATCH] FEAT: Add min version decorator (#5631) Co-authored-by: Maxime Rey <87315832+MaxJPRey@users.noreply.github.com> Co-authored-by: Giulia Malinverno --- .../aedt/core/application/analysis_3d.py | 39 ++++---- src/ansys/aedt/core/application/design.py | 2 +- src/ansys/aedt/core/application/variables.py | 2 +- src/ansys/aedt/core/emit_core/couplings.py | 12 ++- src/ansys/aedt/core/generic/checks.py | 89 +++++++++++++++++++ src/ansys/aedt/core/generic/configurations.py | 2 +- src/ansys/aedt/core/generic/errors.py | 37 ++++++++ .../aedt/core/generic/general_methods.py | 14 +-- .../core/generic/grpc_plugin_dll_class.py | 2 +- src/ansys/aedt/core/hfss3dlayout.py | 5 +- src/ansys/aedt/core/icepak.py | 2 +- .../aedt/core/modeler/cad/component_array.py | 7 +- src/ansys/aedt/core/modeler/cad/object_3d.py | 40 ++++----- src/ansys/aedt/core/modeler/modeler_3d.py | 2 +- .../core/modules/boundary/layout_boundary.py | 2 +- src/ansys/aedt/core/modules/mesh.py | 2 +- src/ansys/aedt/core/modules/mesh_icepak.py | 2 +- .../core/visualization/post/field_data.py | 2 +- .../core/visualization/post/post_icepak.py | 28 +++--- tests/system/solvers/test_00_analyze.py | 6 +- tests/unit/test_utils.py | 88 ++++++++++++++++++ 21 files changed, 290 insertions(+), 95 deletions(-) create mode 100644 src/ansys/aedt/core/generic/checks.py create mode 100644 src/ansys/aedt/core/generic/errors.py diff --git a/src/ansys/aedt/core/application/analysis_3d.py b/src/ansys/aedt/core/application/analysis_3d.py index 2e9013d1ed1..5b69b620b78 100644 --- a/src/ansys/aedt/core/application/analysis_3d.py +++ b/src/ansys/aedt/core/application/analysis_3d.py @@ -27,6 +27,7 @@ import os from ansys.aedt.core.application.analysis import Analysis +from ansys.aedt.core.generic.checks import min_aedt_version from ansys.aedt.core.generic.configurations import Configurations from ansys.aedt.core.generic.constants import unit_converter from ansys.aedt.core.generic.general_methods import generate_unique_name @@ -224,6 +225,7 @@ def components3d(self): return components_dict @pyaedt_function_handler(objects="assignment", export_path="output_file") + @min_aedt_version("2021.2") def plot( self, assignment=None, @@ -280,23 +282,20 @@ def plot( :class:`ansys.aedt.core.generic.plot.ModelPlotter` Model Object. """ - if self._aedt_version < "2021.2": - self.logger.warning("Plot is supported from AEDT 2021 R2.") - else: - return self.post.plot_model_obj( - objects=assignment, - show=show, - export_path=output_file, - plot_as_separate_objects=plot_as_separate_objects, - plot_air_objects=plot_air_objects, - force_opacity_value=force_opacity_value, - clean_files=clean_files, - view=view, - show_legend=show_legend, - dark_mode=dark_mode, - show_bounding=show_bounding, - show_grid=show_grid, - ) + return self.post.plot_model_obj( + objects=assignment, + show=show, + export_path=output_file, + plot_as_separate_objects=plot_as_separate_objects, + plot_air_objects=plot_air_objects, + force_opacity_value=force_opacity_value, + clean_files=clean_files, + view=view, + show_legend=show_legend, + dark_mode=dark_mode, + show_bounding=show_bounding, + show_grid=show_grid, + ) @pyaedt_function_handler(setup_name="setup", variation_string="variations") def export_mesh_stats(self, setup, variations="", mesh_path=None): @@ -1145,6 +1144,7 @@ def flatten_3d_components(self, components=None, purge_history=True, password=No return True @pyaedt_function_handler(object_name="assignment") + @min_aedt_version("2023.2") def identify_touching_conductors(self, assignment=None): # type: (str) -> dict """Identify all touching components and group in a dictionary. @@ -1161,10 +1161,7 @@ def identify_touching_conductors(self, assignment=None): dict """ - if settings.aedt_version < "2023.2": # pragma: no cover - self.logger.error("This method requires CPython and PyVista.") - return {} - if settings.aedt_version >= "2023.2" and self.design_type == "HFSS": # pragma: no cover + if self.design_type == "HFSS": # pragma: no cover nets_aedt = self.oboundary.IdentifyNets(True) nets = {} for net in nets_aedt[1:]: diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index e5b9c1d2b83..4c1ed499a08 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -68,7 +68,7 @@ from ansys.aedt.core.generic.constants import AEDT_UNITS from ansys.aedt.core.generic.constants import unit_system from ansys.aedt.core.generic.data_handlers import variation_string_to_dict -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import check_and_download_file from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import inner_project_settings diff --git a/src/ansys/aedt/core/application/variables.py b/src/ansys/aedt/core/application/variables.py index 84a545533fb..b5f698e2d6b 100644 --- a/src/ansys/aedt/core/application/variables.py +++ b/src/ansys/aedt/core/application/variables.py @@ -50,7 +50,7 @@ from ansys.aedt.core.generic.constants import SI_UNITS from ansys.aedt.core.generic.constants import _resolve_unit_system from ansys.aedt.core.generic.constants import unit_system -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import check_numeric_equivalence from ansys.aedt.core.generic.general_methods import is_array from ansys.aedt.core.generic.general_methods import is_number diff --git a/src/ansys/aedt/core/emit_core/couplings.py b/src/ansys/aedt/core/emit_core/couplings.py index 2b66c40fc1b..1cf762fab0f 100644 --- a/src/ansys/aedt/core/emit_core/couplings.py +++ b/src/ansys/aedt/core/emit_core/couplings.py @@ -27,7 +27,7 @@ This module provides for interacting with EMIT Analysis and Results windows. """ -import warnings +from ansys.aedt.core.generic.checks import min_aedt_version class CouplingsEmit(object): @@ -156,9 +156,12 @@ def update_link(self, coupling_name): self._odesign.UpdateLink(coupling_name) @property + @min_aedt_version("2022.2") def linkable_design_names(self): """List the available link names. + This property is only available in AEDT version 2022.2 or higher. + Parameters ---------- None @@ -174,12 +177,7 @@ def linkable_design_names(self): >>> app = Emit("Apache with multiple links") >>> links = app.couplings.linkable_design_names """ - desktop_version = self._desktop.GetVersion()[0:6] - if desktop_version >= "2022.2": - return self._odesign.GetAvailableLinkNames() - else: - warnings.warn("The function linkable_design_names() requires AEDT 2022 R2 or later.") - return [] + return self._odesign.GetAvailableLinkNames() @property def cad_nodes(self): diff --git a/src/ansys/aedt/core/generic/checks.py b/src/ansys/aedt/core/generic/checks.py new file mode 100644 index 00000000000..50c0b3d356f --- /dev/null +++ b/src/ansys/aedt/core/generic/checks.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Provides functions for performing common checks.""" + +from ansys.aedt.core.generic.errors import AEDTRuntimeError + + +def min_aedt_version(min_version: str): + """Compare a minimum required version to the current AEDT version. + + This decorator should only be used on methods where the associated object can reach the desktop instance. + Otherwise, there is no way to check version compatibility and an error is raised. + + Parameters + ---------- + min_version: str + Minimum AEDT version required by the method. + The value should follow the format YEAR.RELEASE, for example '2024.2'. + + Raises + ------ + + AEDTRuntimeError + If the method version is higher than the AEDT version. + """ + + def fetch_odesktop_from_common_attributes_names(item): + attributes_to_check = ["odesktop", "_odesktop", "_desktop"] + for attribute in attributes_to_check: + odesktop = getattr(item, attribute, None) + if odesktop is not None: + return odesktop + + def fetch_odesktop_from_private_app_attribute(item): + app = getattr(item, f"_{item.__class__.__name__}__app", None) + if app is not None: + return app.odesktop + + def fetch_odesktop_from_desktop_class(item): + attributes_to_check = ["desktop_class", "_desktop_class"] + for attribute in attributes_to_check: + desktop_class = getattr(item, attribute, None) + if desktop_class is not None: + return desktop_class.odesktop + + def aedt_version_decorator(method): + def wrapper(self, *args, **kwargs): + odesktop = ( + fetch_odesktop_from_common_attributes_names(self) + or fetch_odesktop_from_private_app_attribute(self) + or fetch_odesktop_from_desktop_class(self) + ) + if odesktop is None: + raise AEDTRuntimeError("The AEDT desktop object is not available.") + + desktop_version = odesktop.GetVersion() + if desktop_version < min_version: + raise AEDTRuntimeError( + f"The method '{method.__name__}' requires a minimum Ansys release version of " + + f"{min_version}, but the current version used is {desktop_version}." + ) + else: + return method(self, *args, **kwargs) + + return wrapper + + return aedt_version_decorator diff --git a/src/ansys/aedt/core/generic/configurations.py b/src/ansys/aedt/core/generic/configurations.py index d0fe6354e26..1b703d4aa1a 100644 --- a/src/ansys/aedt/core/generic/configurations.py +++ b/src/ansys/aedt/core/generic/configurations.py @@ -32,7 +32,7 @@ from ansys.aedt.core import __version__ from ansys.aedt.core.application.variables import decompose_variable_value from ansys.aedt.core.generic.data_handlers import _arg2dict -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import generate_unique_folder_name from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import open_file diff --git a/src/ansys/aedt/core/generic/errors.py b/src/ansys/aedt/core/generic/errors.py new file mode 100644 index 00000000000..d12450d2d4f --- /dev/null +++ b/src/ansys/aedt/core/generic/errors.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Provide PyAEDT errors.""" + + +class GrpcApiError(RuntimeError): + """Exception raised for errors encountered while interacting with the gRPC API.""" + + +class MethodNotSupportedError(RuntimeError): + """Exception raised when attempting to call a method that is not supported.""" + + +class AEDTRuntimeError(RuntimeError): + """Exception raised for errors occurring during the runtime execution of AEDT scripts.""" diff --git a/src/ansys/aedt/core/generic/general_methods.py b/src/ansys/aedt/core/generic/general_methods.py index 5bdf80d4933..d33ad6e1f28 100644 --- a/src/ansys/aedt/core/generic/general_methods.py +++ b/src/ansys/aedt/core/generic/general_methods.py @@ -47,6 +47,8 @@ from ansys.aedt.core.aedt_logger import pyaedt_logger from ansys.aedt.core.generic.aedt_versions import aedt_versions from ansys.aedt.core.generic.constants import CSS4_COLORS +from ansys.aedt.core.generic.errors import GrpcApiError +from ansys.aedt.core.generic.errors import MethodNotSupportedError from ansys.aedt.core.generic.settings import inner_project_settings # noqa: F401 from ansys.aedt.core.generic.settings import settings import psutil @@ -67,18 +69,6 @@ ] -class GrpcApiError(Exception): - """ """ - - pass - - -class MethodNotSupportedError(Exception): - """ """ - - pass - - def _write_mes(mes_text): mes_text = str(mes_text) parts = [mes_text[i : i + 250] for i in range(0, len(mes_text), 250)] diff --git a/src/ansys/aedt/core/generic/grpc_plugin_dll_class.py b/src/ansys/aedt/core/generic/grpc_plugin_dll_class.py index de715b3d428..701c0e59e32 100644 --- a/src/ansys/aedt/core/generic/grpc_plugin_dll_class.py +++ b/src/ansys/aedt/core/generic/grpc_plugin_dll_class.py @@ -34,7 +34,7 @@ import time import types -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import _retry_ntimes from ansys.aedt.core.generic.general_methods import inclusion_list from ansys.aedt.core.generic.general_methods import settings diff --git a/src/ansys/aedt/core/hfss3dlayout.py b/src/ansys/aedt/core/hfss3dlayout.py index 9bb05b7c92e..2e0fb0770a3 100644 --- a/src/ansys/aedt/core/hfss3dlayout.py +++ b/src/ansys/aedt/core/hfss3dlayout.py @@ -34,6 +34,7 @@ from ansys.aedt.core.application.analysis_3d_layout import FieldAnalysis3DLayout from ansys.aedt.core.application.analysis_hf import ScatteringMethods +from ansys.aedt.core.generic.checks import min_aedt_version from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import open_file from ansys.aedt.core.generic.general_methods import parse_excitation_file @@ -915,6 +916,7 @@ def export_touchstone_on_completion(self, export=True, output_dir=""): return True @pyaedt_function_handler() + @min_aedt_version("2025.1") def set_export_touchstone( self, file_format="TouchStone1.0", @@ -995,9 +997,6 @@ def set_export_touchstone( """ - if settings.aedt_version < "2025.1": - self.logger.warning("Touchstone export setup aborted. This method is available from AEDT 2025.1.") - return False preferences = "Planar EM\\" design_name = self.design_name diff --git a/src/ansys/aedt/core/icepak.py b/src/ansys/aedt/core/icepak.py index 487abc7afb3..947c3f2e675 100644 --- a/src/ansys/aedt/core/icepak.py +++ b/src/ansys/aedt/core/icepak.py @@ -36,7 +36,7 @@ from ansys.aedt.core.generic.data_handlers import _arg2dict from ansys.aedt.core.generic.data_handlers import _dict2arg from ansys.aedt.core.generic.data_handlers import random_string -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import open_file from ansys.aedt.core.generic.general_methods import pyaedt_function_handler diff --git a/src/ansys/aedt/core/modeler/cad/component_array.py b/src/ansys/aedt/core/modeler/cad/component_array.py index bd54b5caec7..d5e5d84373e 100644 --- a/src/ansys/aedt/core/modeler/cad/component_array.py +++ b/src/ansys/aedt/core/modeler/cad/component_array.py @@ -27,6 +27,7 @@ import os import re +from ansys.aedt.core.generic.checks import min_aedt_version from ansys.aedt.core.generic.constants import AEDT_UNITS from ansys.aedt.core.generic.general_methods import _uname from ansys.aedt.core.generic.general_methods import pyaedt_function_handler @@ -368,6 +369,7 @@ def delete(self): self.__app.component_array_names = list(self.__app.get_oo_name(self.__app.odesign, "Model")) @pyaedt_function_handler(array_path="output_file") + @min_aedt_version("2024.1") def export_array_info(self, output_file=None): # pragma: no cover """Export array information to a CSV file. @@ -379,12 +381,7 @@ def export_array_info(self, output_file=None): # pragma: no cover References ---------- >>> oModule.ExportArray - """ - if self.__app.settings.aedt_version < "2024.1": # pragma: no cover - self.logger.warning(f"This feature is not available in {str(self.__app.settings.aedt_version)}.") - return False - if not output_file: # pragma: no cover output_file = os.path.join(self.__app.toolkit_directory, "array_info.csv") self.__app.omodelsetup.ExportArray(self.name, output_file) diff --git a/src/ansys/aedt/core/modeler/cad/object_3d.py b/src/ansys/aedt/core/modeler/cad/object_3d.py index 0a23e4e7a0a..14e07b7d6db 100644 --- a/src/ansys/aedt/core/modeler/cad/object_3d.py +++ b/src/ansys/aedt/core/modeler/cad/object_3d.py @@ -37,6 +37,7 @@ import os import re +from ansys.aedt.core.generic.checks import min_aedt_version from ansys.aedt.core.generic.constants import AEDT_UNITS from ansys.aedt.core.generic.general_methods import _to_boolean from ansys.aedt.core.generic.general_methods import _uname @@ -246,6 +247,7 @@ def _odesign(self): return self._primitives._modeler._app._odesign @pyaedt_function_handler() + @min_aedt_version("2021.2") def plot(self, show=True): """Plot model with PyVista. @@ -263,15 +265,15 @@ def plot(self, show=True): ----- Works from AEDT 2021.2 in CPython only. PyVista has to be installed. """ - if self._primitives._app._aedt_version >= "2021.2": - return self._primitives._app.post.plot_model_obj( - objects=[self.name], - plot_as_separate_objects=True, - plot_air_objects=True, - show=show, - ) + return self._primitives._app.post.plot_model_obj( + objects=[self.name], + plot_as_separate_objects=True, + plot_air_objects=True, + show=show, + ) @pyaedt_function_handler(file_path="output_file") + @min_aedt_version("2021.2") def export_image(self, output_file=None): """Export the current object to a specified file path. @@ -290,19 +292,17 @@ def export_image(self, output_file=None): str File path. """ - if self._primitives._app._aedt_version >= "2021.2": - if not output_file: - output_file = os.path.join(self._primitives._app.working_directory, self.name + ".png") - model_obj = self._primitives._app.post.plot_model_obj( - objects=[self.name], - show=False, - export_path=output_file, - plot_as_separate_objects=True, - clean_files=True, - ) - if model_obj: - return model_obj.image_file - return False + if not output_file: + output_file = os.path.join(self._primitives._app.working_directory, self.name + ".png") + model_obj = self._primitives._app.post.plot_model_obj( + objects=[self.name], + show=False, + export_path=output_file, + plot_as_separate_objects=True, + clean_files=True, + ) + if model_obj: + return model_obj.image_file @pyaedt_function_handler() def touching_conductors(self): diff --git a/src/ansys/aedt/core/modeler/modeler_3d.py b/src/ansys/aedt/core/modeler/modeler_3d.py index 820e74ba00b..73e8c283898 100644 --- a/src/ansys/aedt/core/modeler/modeler_3d.py +++ b/src/ansys/aedt/core/modeler/modeler_3d.py @@ -31,7 +31,7 @@ import warnings from ansys.aedt.core.application.variables import generate_validation_errors -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import open_file from ansys.aedt.core.generic.general_methods import pyaedt_function_handler diff --git a/src/ansys/aedt/core/modules/boundary/layout_boundary.py b/src/ansys/aedt/core/modules/boundary/layout_boundary.py index fe855892fa4..afa71c2eb8c 100644 --- a/src/ansys/aedt/core/modules/boundary/layout_boundary.py +++ b/src/ansys/aedt/core/modules/boundary/layout_boundary.py @@ -24,7 +24,7 @@ from ansys.aedt.core.generic.data_handlers import _dict2arg from ansys.aedt.core.generic.data_handlers import random_string -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.modeler.cad.elements_3d import BinaryTreeNode from ansys.aedt.core.modules.boundary.common import BoundaryCommon diff --git a/src/ansys/aedt/core/modules/mesh.py b/src/ansys/aedt/core/modules/mesh.py index f4f23bd3e79..c24109f9fcb 100644 --- a/src/ansys/aedt/core/modules/mesh.py +++ b/src/ansys/aedt/core/modules/mesh.py @@ -31,7 +31,7 @@ from ansys.aedt.core.application.design_solutions import model_names from ansys.aedt.core.generic.data_handlers import _dict2arg -from ansys.aedt.core.generic.general_methods import MethodNotSupportedError +from ansys.aedt.core.generic.errors import MethodNotSupportedError from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.general_methods import settings diff --git a/src/ansys/aedt/core/modules/mesh_icepak.py b/src/ansys/aedt/core/modules/mesh_icepak.py index 3b688f41152..1e69c2bc9de 100644 --- a/src/ansys/aedt/core/modules/mesh_icepak.py +++ b/src/ansys/aedt/core/modules/mesh_icepak.py @@ -26,7 +26,7 @@ import warnings from ansys.aedt.core.generic.data_handlers import _dict2arg -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import _dim_arg from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import pyaedt_function_handler diff --git a/src/ansys/aedt/core/visualization/post/field_data.py b/src/ansys/aedt/core/visualization/post/field_data.py index 2882fae70c6..efc01da5ea6 100644 --- a/src/ansys/aedt/core/visualization/post/field_data.py +++ b/src/ansys/aedt/core/visualization/post/field_data.py @@ -34,7 +34,7 @@ from ansys.aedt.core.generic.constants import AllowedMarkers from ansys.aedt.core.generic.constants import EnumUnits from ansys.aedt.core.generic.data_handlers import _dict2arg -from ansys.aedt.core.generic.general_methods import GrpcApiError +from ansys.aedt.core.generic.errors import GrpcApiError from ansys.aedt.core.generic.general_methods import check_and_download_file from ansys.aedt.core.generic.general_methods import open_file from ansys.aedt.core.generic.general_methods import pyaedt_function_handler diff --git a/src/ansys/aedt/core/visualization/post/post_icepak.py b/src/ansys/aedt/core/visualization/post/post_icepak.py index d093a675751..bc9d29fee1e 100644 --- a/src/ansys/aedt/core/visualization/post/post_icepak.py +++ b/src/ansys/aedt/core/visualization/post/post_icepak.py @@ -34,10 +34,10 @@ import os import re +from ansys.aedt.core.generic.checks import min_aedt_version from ansys.aedt.core.generic.general_methods import generate_unique_name from ansys.aedt.core.generic.general_methods import open_file from ansys.aedt.core.generic.general_methods import pyaedt_function_handler -from ansys.aedt.core.generic.settings import settings from ansys.aedt.core.visualization.post.field_summary import FieldSummary from ansys.aedt.core.visualization.post.field_summary import TOTAL_QUANTITIES from ansys.aedt.core.visualization.post.post_common_3d import PostProcessor3D @@ -283,6 +283,7 @@ def evaluate_boundary_quantity( return self._parse_field_summary_content(fs, setup_name, variations, quantity) @pyaedt_function_handler(monitor_name="monitor", quantity_name="quantity", design_variation="variations") + @min_aedt_version("2024.1") def evaluate_monitor_quantity( self, monitor, quantity, side="Default", setup_name=None, variations=None, ref_temperature="", time="0s" ): @@ -322,20 +323,17 @@ def evaluate_monitor_quantity( """ if variations is None: variations = {} - if settings.aedt_version < "2024.1": - raise NotImplementedError("Monitors are not supported in field summary in versions earlier than 2024 R1.") - else: # pragma: no cover - if self._app.monitor.face_monitors.get(monitor, None): - field_type = "Surface" - elif self._app.monitor.point_monitors.get(monitor, None): - field_type = "Volume" - else: - raise AttributeError(f"Monitor {monitor} is not found in the design.") - fs = self.create_field_summary() - fs.add_calculation( - "Monitor", field_type, monitor, quantity, side=side, ref_temperature=ref_temperature, time=time - ) - return self._parse_field_summary_content(fs, setup_name, variations, quantity) + if self._app.monitor.face_monitors.get(monitor, None): + field_type = "Surface" + elif self._app.monitor.point_monitors.get(monitor, None): + field_type = "Volume" + else: + raise AttributeError(f"Monitor {monitor} is not found in the design.") + fs = self.create_field_summary() + fs.add_calculation( + "Monitor", field_type, monitor, quantity, side=side, ref_temperature=ref_temperature, time=time + ) + return self._parse_field_summary_content(fs, setup_name, variations, quantity) @pyaedt_function_handler(design_variation="variations") def evaluate_object_quantity( diff --git a/tests/system/solvers/test_00_analyze.py b/tests/system/solvers/test_00_analyze.py index 53b77bc6fe6..e659253ae49 100644 --- a/tests/system/solvers/test_00_analyze.py +++ b/tests/system/solvers/test_00_analyze.py @@ -33,6 +33,7 @@ from ansys.aedt.core import Icepak from ansys.aedt.core import Maxwell3d from ansys.aedt.core import Rmxprt +from ansys.aedt.core.generic.errors import AEDTRuntimeError from ansys.aedt.core.generic.settings import is_linux from ansys.aedt.core.visualization.post.spisim import SpiSim import pytest @@ -322,7 +323,7 @@ def test_03a_icepak_analyze_and_export_summary(self): out = self.icepak_app.post.evaluate_boundary_quantity(opening.name, "Ux") assert out["Mean"] if self.icepak_app.settings.aedt_version < "2024.1": - with pytest.raises(NotImplementedError): + with pytest.raises(AEDTRuntimeError): self.icepak_app.post.evaluate_monitor_quantity("test_monitor2", "Temperature") else: out = self.icepak_app.post.evaluate_monitor_quantity("test_monitor2", "Temperature") @@ -415,7 +416,8 @@ def test_04b_3dl_analyze_setup(self): if desktop_version > "2024.2": assert self.hfss3dl_solve.set_export_touchstone() else: - assert not self.hfss3dl_solve.set_export_touchstone() + with pytest.raises(AEDTRuntimeError): + self.hfss3dl_solve.set_export_touchstone() assert self.hfss3dl_solve.analyze_setup("Setup1", cores=4, blocking=False) assert self.hfss3dl_solve.are_there_simulations_running assert self.hfss3dl_solve.stop_simulations() diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 7e29982b83f..1db36e5d092 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -27,10 +27,13 @@ import logging import os +import time from unittest.mock import MagicMock from unittest.mock import PropertyMock from unittest.mock import patch +from ansys.aedt.core.generic.checks import AEDTRuntimeError +from ansys.aedt.core.generic.checks import min_aedt_version from ansys.aedt.core.generic.general_methods import pyaedt_function_handler from ansys.aedt.core.generic.settings import ALLOWED_AEDT_ENV_VAR_SETTINGS from ansys.aedt.core.generic.settings import ALLOWED_GENERAL_SETTINGS @@ -44,6 +47,10 @@ SETTINGS_ENABLE_ERROR_HANDLER = settings.enable_error_handler ERROR_MESSAGE = "Dummy message." TOML_DATA = {"key_0": "dummy", "key_1": 12, "key_2": [1, 2], "key_3": {"key_4": 42}} +CURRENT_YEAR = current_year = time.localtime().tm_year +CURRENT_YEAR_VERSION = f"{CURRENT_YEAR}.2" +NEXT_YEAR_VERSION = f"{CURRENT_YEAR + 1}.2" +PREVIOUS_YEAR_VERSION = f"{CURRENT_YEAR - 1}.2" @pytest.fixture(scope="module", autouse=True) @@ -266,3 +273,84 @@ def test_write_toml(tmp_path): _create_toml_file(TOML_DATA, file_path) assert file_path.exists() + + +def test_min_aedt_version_success_with_common_attributes_names(): + class Dummy: + """Dummy class to test min version with common attribute.""" + + odesktop = MagicMock() + odesktop.GetVersion.return_value = CURRENT_YEAR_VERSION + + @min_aedt_version(PREVIOUS_YEAR_VERSION) + def old_method(self): + pass + + dummy = Dummy() + dummy.old_method() + + +def test_min_aedt_version_success_with_app_private_attribute(): + class Dummy: + """Dummy class to test min version with __app attribute.""" + + odesktop = MagicMock() + odesktop.GetVersion.return_value = CURRENT_YEAR_VERSION + __app = odesktop + + @min_aedt_version(PREVIOUS_YEAR_VERSION) + def old_method(self): + pass + + dummy = Dummy() + dummy.old_method() + + +def test_min_aedt_version_success_with_desktop_class(): + class Dummy: + """Dummy class to test min version with __app attribute.""" + + odesktop = MagicMock() + odesktop.GetVersion.return_value = CURRENT_YEAR_VERSION + desktop_class = MagicMock() + desktop_class.odesktop = odesktop + + @min_aedt_version(PREVIOUS_YEAR_VERSION) + def old_method(self): + pass + + dummy = Dummy() + dummy.old_method() + + +def test_min_aedt_version_raise_error_on_future_version(): + class Dummy: + """Dummy class to test min version.""" + + odesktop = MagicMock() + odesktop.GetVersion.return_value = CURRENT_YEAR_VERSION + + @min_aedt_version(NEXT_YEAR_VERSION) + def future_method(self): + pass + + dummy = Dummy() + pattern = ( + f"The method 'future_method' requires a minimum Ansys release version of {NEXT_YEAR_VERSION}, " + "but the current version used is .+" + ) + + with pytest.raises(AEDTRuntimeError, match=pattern): + dummy.future_method() + + +def test_min_aedt_version_raise_error_on_non_decorable_object(): + class Dummy: + @min_aedt_version(PREVIOUS_YEAR_VERSION) + def dummy_method(self): + pass + + dummy = Dummy() + + with pytest.raises(AEDTRuntimeError, match="The AEDT desktop object is not available."): + dummy.dummy_method()