diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 04ee0881842b..ce605660dd6d 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -1096,7 +1096,7 @@ jobs: TESTS_VERSION: ${{ env.TESTS_VERSION }} run: | .venv\Scripts\Activate.ps1 - uv sync --frozen --group tests --extra all + uv sync --frozen --group tests --extra all-dotnet - name: Remove Ansys processes (if any) shell: powershell diff --git a/.github/workflows/ci_cd.yml b/.github/workflows/ci_cd.yml index 92f79d1439cb..a6706cbec590 100644 --- a/.github/workflows/ci_cd.yml +++ b/.github/workflows/ci_cd.yml @@ -1250,7 +1250,7 @@ jobs: TESTS_VERSION: ${{ env.TESTS_VERSION }} run: | .venv\Scripts\Activate.ps1 - uv sync --frozen --group tests --extra all + uv sync --frozen --group tests --extra all-dotnet - name: Wait for master cache update uses: ./.github/actions/check-cache diff --git a/.github/workflows/manual_draft.yml b/.github/workflows/manual_draft.yml index 0223ef251e65..f02fe409478f 100644 --- a/.github/workflows/manual_draft.yml +++ b/.github/workflows/manual_draft.yml @@ -712,6 +712,7 @@ jobs: run: | .venv\Scripts\Activate.ps1 pip install . --group tests + pip install "pywin32>=303" "ansys-pythonnet>=3.1.0rc3" - name: Remove Ansys processes (if any) shell: powershell diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml index c85c46850625..ce347f18deb5 100644 --- a/.github/workflows/nightly-tests.yml +++ b/.github/workflows/nightly-tests.yml @@ -1015,7 +1015,7 @@ jobs: TESTS_VERSION: ${{ env.TESTS_VERSION }} run: | .venv\Scripts\Activate.ps1 - uv sync --frozen --group tests --extra all + uv sync --frozen --group tests --extra all-dotnet - name: Remove Ansys processes (if any) shell: powershell diff --git a/.github/workflows/sync-release.yml b/.github/workflows/sync-release.yml index a4b6ff7d5ed5..49e184543a4a 100644 --- a/.github/workflows/sync-release.yml +++ b/.github/workflows/sync-release.yml @@ -1254,7 +1254,7 @@ jobs: - name: Install pyaedt and tests dependencies run: | .venv\Scripts\Activate.ps1 - uv sync --frozen --group tests --extra all + uv sync --frozen --group tests --extra all-dotnet - name: Remove Ansys processes (if any) shell: powershell diff --git a/.github/workflows/update-testmondata-cache.yml b/.github/workflows/update-testmondata-cache.yml index a4b89ff25774..dc1474fd0dfa 100644 --- a/.github/workflows/update-testmondata-cache.yml +++ b/.github/workflows/update-testmondata-cache.yml @@ -963,7 +963,7 @@ jobs: TESTS_VERSION: ${{ env.TESTS_VERSION }} run: | .venv\Scripts\Activate.ps1 - uv sync --frozen --group tests --extra all + uv sync --frozen --group tests --extra all-dotnet - name: Restore testmondata cache uses: ./.github/actions/testmon-cache diff --git a/.github/workflows/weekly-tests.yml b/.github/workflows/weekly-tests.yml index 5be88d1eb23a..afb323f3dcab 100644 --- a/.github/workflows/weekly-tests.yml +++ b/.github/workflows/weekly-tests.yml @@ -993,7 +993,7 @@ jobs: TESTS_VERSION: ${{ env.TESTS_VERSION }} run: | .venv\Scripts\Activate.ps1 - uv sync --frozen --group tests --extra all + uv sync --frozen --group tests --extra all-dotnet - name: Remove Ansys processes (if any) diff --git a/doc/changelog.d/7860.fixed.md b/doc/changelog.d/7860.fixed.md new file mode 100644 index 000000000000..f3aca34d183f --- /dev/null +++ b/doc/changelog.d/7860.fixed.md @@ -0,0 +1 @@ +Pyaedt test export to aedt test fail diff --git a/pyproject.toml b/pyproject.toml index 05bf9acca2f4..4d19a481e754 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,6 +131,8 @@ tests = [ "pytest-xdist>=3.5.0", "pytest-testmon>=2.0.0", "cryptography", + "ansys-pythonnet>=3.1.0rc3; platform_system=='Windows'", + "pywin32>=303; platform_system=='Windows'", ] doc = [ "ansys-sphinx-theme>=1.0.0", @@ -365,3 +367,9 @@ directory = "test" name = "Test" showcontent = true +[tool.pyright] +include = ["src", "tests"] +venvPath = "." +venv = ".venv" +pythonVersion = "3.10" +extraPaths = ["src"] diff --git a/src/ansys/aedt/core/application/design.py b/src/ansys/aedt/core/application/design.py index 6c0cfd3dac84..8c8e42881290 100644 --- a/src/ansys/aedt/core/application/design.py +++ b/src/ansys/aedt/core/application/design.py @@ -1211,7 +1211,7 @@ def odesign(self) -> object: >>> oProject.SetActiveDesign >>> oProject.InsertDesign """ - if settings.use_multi_desktop: # pragma: no cover + if settings.use_multi_desktop and self._desktop_class.is_grpc_api: # pragma: no cover self._desktop_class.grpc_plugin.recreate_application(True) if self._design_name: self._odesign = self.oproject.SetActiveDesign(self._design_name) @@ -1258,7 +1258,7 @@ def oproject(self) -> object: >>> oDesktop.SetActiveProject >>> oDesktop.NewProject """ - if settings.use_multi_desktop: # pragma: no cover + if settings.use_multi_desktop and self._desktop_class.is_grpc_api: # pragma: no cover self._desktop_class.grpc_plugin.recreate_application(True) return self._oproject diff --git a/src/ansys/aedt/core/desktop.py b/src/ansys/aedt/core/desktop.py index 7bfab3479a65..dcb9e51d5ed3 100644 --- a/src/ansys/aedt/core/desktop.py +++ b/src/ansys/aedt/core/desktop.py @@ -1197,7 +1197,7 @@ def odesktop(self) -> object: >>> d = Desktop() >>> d.odesktop """ - if settings.use_grpc_api: + if self.is_grpc_api: tries = 0 while tries < 5: try: diff --git a/src/ansys/aedt/core/filtersolutions.py b/src/ansys/aedt/core/filtersolutions.py index 2133dfd385dc..67887542d3d7 100644 --- a/src/ansys/aedt/core/filtersolutions.py +++ b/src/ansys/aedt/core/filtersolutions.py @@ -64,10 +64,6 @@ def __init__(self, version: str | None = None) -> None: ) FilterDesignBase._active_design.close() - if version is not None: - # Clear global state before initialization - settings.aedt_version = None - FilterDesignBase._active_design = self self.version = version if version else settings.aedt_version @@ -122,16 +118,23 @@ def _create_design(self, desktop_version, desktop_process_id): ------- :class:``AEDT`` design object """ - settings.use_grpc_api = None - if isinstance(FilterDesignBase._active_design, LumpedDesign): - return Circuit(version=desktop_version, aedt_process_id=desktop_process_id) - elif isinstance(FilterDesignBase._active_design, DistributedDesign): - if getattr(self, "insert_hfss_3dl_design", True): - return Hfss3dLayout(version=desktop_version, aedt_process_id=desktop_process_id) - elif getattr(self, "insert_hfss_design", True): - return Hfss(version=desktop_version, aedt_process_id=desktop_process_id) - elif getattr(self, "insert_circuit_design", True): + # Preserve the current version to ensure proper session reconnection. + original_version = settings.aedt_version + try: + settings.aedt_version = desktop_version + if isinstance(FilterDesignBase._active_design, LumpedDesign): return Circuit(version=desktop_version, aedt_process_id=desktop_process_id) + elif isinstance(FilterDesignBase._active_design, DistributedDesign): + if getattr(self, "insert_hfss_3dl_design", True): + return Hfss3dLayout(version=desktop_version, aedt_process_id=desktop_process_id) + elif getattr(self, "insert_hfss_design", True): + return Hfss(version=desktop_version, aedt_process_id=desktop_process_id) + elif getattr(self, "insert_circuit_design", True): + return Circuit(version=desktop_version, aedt_process_id=desktop_process_id) + return None + finally: + # Restore the original version in case of early exit or exception. + settings.aedt_version = original_version class LumpedDesign(FilterDesignBase): diff --git a/src/ansys/aedt/core/filtersolutions_core/dll_interface.py b/src/ansys/aedt/core/filtersolutions_core/dll_interface.py index 5d093b4c9383..485af9185476 100644 --- a/src/ansys/aedt/core/filtersolutions_core/dll_interface.py +++ b/src/ansys/aedt/core/filtersolutions_core/dll_interface.py @@ -65,13 +65,17 @@ def _init_dll_path(self, version): ) self.dll_path = os.path.join(aedt_versions.installed_versions[version], "nuhertz", "FilterSolutionsAPI.dll") print("DLL Path:", self.dll_path) + # Ensure the DLL directory is in PATH so Borland BPL dependencies are found + dll_dir = os.path.dirname(self.dll_path) + if dll_dir not in os.environ.get("PATH", ""): + os.environ["PATH"] = dll_dir + os.pathsep + os.environ.get("PATH", "") if not os.path.isfile(self.dll_path): raise RuntimeError(f"The 'FilterSolutions' API DLL was not found at {self.dll_path}.") # pragma: no cover self._version = version def _init_dll(self, show_gui) -> None: """Load DLL and initialize application parameters to default values.""" - self._dll = ctypes.cdll.LoadLibrary(self.dll_path) + self._dll = ctypes.CDLL(self.dll_path, winmode=0) self._define_dll_functions() self.show_gui = show_gui if show_gui: # pragma: no cover diff --git a/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py b/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py index 1c45683d8498..7bbfda034d06 100644 --- a/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py +++ b/src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py @@ -1371,15 +1371,15 @@ def substrate_er(self) -> SubstrateType | str: substrate_er_value_str = create_string_buffer(100) status = self._dll.getEr(substrate_er_value_str, byref(substrate_er_index), 100) self._dll_interface.raise_error(status) - if substrate_er_index.value in [e.value for e in SubstrateEr]: + if substrate_er_index.value in SubstrateEr._value2member_map_: return SubstrateEr(substrate_er_index.value) else: return substrate_er_value_str.value.decode("ascii") @substrate_er.setter def substrate_er(self, substrate_input: SubstrateEr | str) -> None: - if substrate_input in list(SubstrateEr): - substrate_er_index = SubstrateEr(substrate_input).value + if isinstance(substrate_input, SubstrateEr): + substrate_er_index = substrate_input.value substrate_er_value = "" elif isinstance(substrate_input, str): substrate_er_value = substrate_input @@ -1405,15 +1405,15 @@ def substrate_resistivity(self) -> SubstrateResistivity | str: substrate_resistivity_value_str = create_string_buffer(100) status = self._dll.getResistivity(substrate_resistivity_value_str, byref(substrate_resistivity_index), 100) self._dll_interface.raise_error(status) - if substrate_resistivity_index.value in [e.value for e in SubstrateResistivity]: + if substrate_resistivity_index.value in SubstrateResistivity._value2member_map_: return SubstrateResistivity(substrate_resistivity_index.value) else: return substrate_resistivity_value_str.value.decode("ascii") @substrate_resistivity.setter def substrate_resistivity(self, substrate_input: SubstrateResistivity | str) -> None: - if substrate_input in list(SubstrateResistivity): - substrate_resistivity_index = SubstrateResistivity(substrate_input).value + if isinstance(substrate_input, SubstrateResistivity): + substrate_resistivity_index = substrate_input.value substrate_resistivity_value = "" elif isinstance(substrate_input, str): substrate_resistivity_value = substrate_input @@ -1439,15 +1439,15 @@ def substrate_loss_tangent(self) -> SubstrateEr | str: substrate_loss_tangent_value_str = create_string_buffer(100) status = self._dll.getLossTangent(substrate_loss_tangent_value_str, byref(substrate_loss_tangent_index), 100) self._dll_interface.raise_error(status) - if substrate_loss_tangent_index.value in [e.value for e in SubstrateEr]: + if substrate_loss_tangent_index.value in SubstrateEr._value2member_map_: return SubstrateEr(substrate_loss_tangent_index.value) else: return substrate_loss_tangent_value_str.value.decode("ascii") @substrate_loss_tangent.setter def substrate_loss_tangent(self, substrate_input: SubstrateEr | str) -> None: - if substrate_input in list(SubstrateEr): - substrate_loss_tangent_index = SubstrateEr(substrate_input).value + if isinstance(substrate_input, SubstrateEr): + substrate_loss_tangent_index = substrate_input.value substrate_loss_tangent_value = "" elif isinstance(substrate_input, str): substrate_loss_tangent_value = substrate_input diff --git a/tests/system/filter_solutions/conftest.py b/tests/system/filter_solutions/conftest.py index 120064a4ba85..4a73fc7c38f5 100644 --- a/tests/system/filter_solutions/conftest.py +++ b/tests/system/filter_solutions/conftest.py @@ -26,9 +26,11 @@ import pytest +from ansys.aedt.core import settings from ansys.aedt.core.filtersolutions import DistributedDesign from ansys.aedt.core.filtersolutions import LumpedDesign from tests.conftest import DESKTOP_VERSION +from tests.conftest import USE_GRPC # Ensure US English locale (with cross-platform fallbacks) for _loc in ("en_US.UTF-8", "English_United States.1252", "en_US"): @@ -45,10 +47,12 @@ @pytest.fixture def lumped_design(): """Fixture for creating a LumpedDesign object.""" + settings.use_grpc_api = USE_GRPC return LumpedDesign(DESKTOP_VERSION) @pytest.fixture def distributed_design(): """Fixture for creating a DistributedDesign object.""" + settings.use_grpc_api = USE_GRPC return DistributedDesign(DESKTOP_VERSION) diff --git a/tests/system/filter_solutions/test_desktop_types.py b/tests/system/filter_solutions/test_desktop_types.py index ab6baa4c6211..48982255ca2b 100644 --- a/tests/system/filter_solutions/test_desktop_types.py +++ b/tests/system/filter_solutions/test_desktop_types.py @@ -29,8 +29,10 @@ from ansys.aedt.core import Circuit from ansys.aedt.core import Hfss from ansys.aedt.core import Hfss3dLayout +from ansys.aedt.core.filtersolutions_core.export_to_aedt import PartLibraries from ansys.aedt.core.generic.settings import is_linux from tests.conftest import DESKTOP_VERSION +from tests.system.filter_solutions.resources import read_resource_file ON_CI = os.getenv("ON_CI", "false").lower() == "true" @@ -53,6 +55,16 @@ def test_lumped_exported_desktop(self, lumped_design): assert variables["C3"].si_value == pytest.approx(6.366e-12) circuit.desktop_class.close_desktop() + def test_import_tuned_variables(self, lumped_design): + lumped_design.export_to_aedt.simulate_after_export_enabled = True + lumped_design.export_to_aedt.optimize_after_export_enabled = True + lumped_design.export_to_aedt.part_libraries = PartLibraries.LUMPED + circuit = lumped_design.export_to_aedt.export_design() + assert lumped_design.export_to_aedt.import_tuned_variables().splitlines() == read_resource_file( + "imported_netlist.ckt", "Lumped" + ) + circuit.desktop_class.close_desktop() + def test_distributed_circuit_exported_desktop(self, distributed_design): schem_name = distributed_design.export_to_aedt.schematic_name schem_name_length = len(schem_name) diff --git a/tests/system/filter_solutions/test_export_to_aedt/test_export_to_aedt.py b/tests/system/filter_solutions/test_export_to_aedt/test_export_to_aedt.py index c326a8f2fdb3..075535170ec5 100644 --- a/tests/system/filter_solutions/test_export_to_aedt/test_export_to_aedt.py +++ b/tests/system/filter_solutions/test_export_to_aedt/test_export_to_aedt.py @@ -35,7 +35,6 @@ from ansys.aedt.core.generic.settings import is_linux from tests.conftest import DESKTOP_VERSION from tests.conftest import SKIP_MODELITHICS -from tests.system.filter_solutions.resources import read_resource_file from tests.system.filter_solutions.resources import resource_path ON_CI = os.getenv("ON_CI", "false").lower() == "true" @@ -474,17 +473,6 @@ def test_save_library_parts_config(self, lumped_design): assert lumped_design.export_to_aedt.substrate_dielectric_height == "3 mm" assert lumped_design.export_to_aedt.substrate_loss_tangent == "0.065 " - @pytest.mark.skipif(DESKTOP_VERSION < "2026.1", reason="Skipped on versions earlier than 2026.1") - def test_import_tuned_variables(self, lumped_design): - lumped_design.export_to_aedt.simulate_after_export_enabled = True - lumped_design.export_to_aedt.optimize_after_export_enabled = True - lumped_design.export_to_aedt.part_libraries = PartLibraries.LUMPED - app = lumped_design.export_to_aedt.export_design() - assert lumped_design.export_to_aedt.import_tuned_variables().splitlines() == read_resource_file( - "imported_netlist.ckt", "Lumped" - ) - app.desktop_class.close_desktop() - def test_part_libraries(self, lumped_design): assert lumped_design.export_to_aedt.part_libraries == PartLibraries.LUMPED assert len(PartLibraries) == 3 diff --git a/tests/unit/test_desktop.py b/tests/unit/test_desktop.py index f588327aa890..65972811161e 100644 --- a/tests/unit/test_desktop.py +++ b/tests/unit/test_desktop.py @@ -102,6 +102,7 @@ def test_get_available_toolkits() -> None: def test_desktop_odesktop_retries(mock_settings, mock_sleep, mock_desktop) -> None: """Test Desktop.odesktop property retries to get the odesktop object.""" desktop = Desktop() + desktop.is_grpc_api = True desktop.grpc_plugin = MagicMock() aedt_app = MagicMock() mock_odesktop = PropertyMock(name="oui", side_effect=[Exception("Failure"), aedt_app]) @@ -116,6 +117,7 @@ def test_desktop_odesktop_retries(mock_settings, mock_sleep, mock_desktop) -> No def test_desktop_odesktop_setter(mock_desktop) -> None: """Test Desktop.odesktop property retries to get the odesktop object.""" desktop = Desktop() + desktop.is_grpc_api = False aedt_app = MagicMock() desktop.grpc_plugin = MagicMock() diff --git a/uv.lock b/uv.lock index 3cc026e1f4da..ec8315e050d2 100644 --- a/uv.lock +++ b/uv.lock @@ -3728,6 +3728,8 @@ tests = [ { name = "pytest-testmon", specifier = ">=2.0.0" }, { name = "pytest-timeout", specifier = ">=2.3.0" }, { name = "pytest-xdist", specifier = ">=3.5.0" }, + { name = "ansys-pythonnet", marker = "sys_platform == 'win32'", specifier = ">=3.1.0rc3" }, + { name = "pywin32", marker = "sys_platform == 'win32'", specifier = ">=303" }, ] [[package]]