Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
acc8e34
D1457509: Commit with new github id
ramin4667 Jun 30, 2026
50826c5
D1457509: Comments are applied.
ramin4667 Jul 3, 2026
2b20f0c
Merge branch 'main' into D1457509__PyAEDT_test_export_to_aedt_test_FA…
ramin4667 Jul 3, 2026
06f2a89
chore: adding changelog file 7861.fixed.md [dependabot-skip]
pyansys-ci-bot Jul 3, 2026
1e36d7e
Merge branch 'main' into D1457509__PyAEDT_test_export_to_aedt_test_FA…
Samuelopez-ansys Jul 7, 2026
34433b4
D1457509: Disable GRPC use for FilterSolutions
ramin4667 Jul 7, 2026
13942f9
Merge branch 'D1457509__PyAEDT_test_export_to_aedt_test_FAIL_New' of …
ramin4667 Jul 7, 2026
5ee46f2
Merge branch 'main' into D1457509__PyAEDT_test_export_to_aedt_test_FA…
ramin4667 Jul 7, 2026
dadb7d9
Merge branch 'main' into D1457509__PyAEDT_test_export_to_aedt_test_FA…
ramin4667 Jul 9, 2026
ccd105c
Merge branch 'main' into D1457509__PyAEDT_test_export_to_aedt_test_FA…
ramin4667 Jul 17, 2026
02b4a2e
D1457509: Remove ONCI skip line
ramin4667 Jul 17, 2026
72c9eb8
Merge branch 'D1457509__PyAEDT_test_export_to_aedt_test_FAIL_New' of …
ramin4667 Jul 17, 2026
be59a68
D1457509: Release desktop added
ramin4667 Jul 21, 2026
faf3903
Merge branch 'main' into D1457509__PyAEDT_test_export_to_aedt_test_FA…
ramin4667 Jul 21, 2026
2602f05
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Jul 21, 2026
abcb205
D1457509: Fix import
ramin4667 Jul 21, 2026
892ed82
Merge branch 'D1457509__PyAEDT_test_export_to_aedt_test_FAIL_New' of …
ramin4667 Jul 21, 2026
fb7bc51
CHORE: Auto fixes from pre-commit hooks
pre-commit-ci[bot] Jul 21, 2026
073f873
D1457509: Release retry fix
ramin4667 Jul 21, 2026
1c307f0
Merge branch 'D1457509__PyAEDT_test_export_to_aedt_test_FAIL_New' of …
ramin4667 Jul 21, 2026
32e855c
Merge branch 'main' into D1457509__PyAEDT_test_export_to_aedt_test_FA…
ramin4667 Jul 21, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/ci-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,7 @@ jobs:
env:
TESTS_VERSION: ${{ env.TESTS_VERSION }}
run: |
uv sync --frozen --group tests --extra all
uv sync --frozen --group tests --extra all-dotnet

- name: Remove Ansys processes (if any)
shell: powershell
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1341,7 +1341,7 @@ jobs:
env:
TESTS_VERSION: ${{ env.TESTS_VERSION }}
run: |
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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/manual_draft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ jobs:
- name: Install pyaedt and tests dependencies
run: |
.venv\Scripts\Activate.ps1
pip install . --group tests
pip install ".[all-dotnet]" --group tests

- name: Remove Ansys processes (if any)
shell: powershell
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nightly-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -988,7 +988,7 @@ jobs:
env:
TESTS_VERSION: ${{ env.TESTS_VERSION }}
run: |
uv sync --frozen --group tests --extra all
uv sync --frozen --group tests --extra all-dotnet

- name: Remove Ansys processes (if any)
shell: powershell
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sync-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1228,7 +1228,7 @@ jobs:

- name: Install pyaedt and tests dependencies
run: |
uv sync --frozen --group tests --extra all
uv sync --frozen --group tests --extra all-dotnet

- name: Remove Ansys processes (if any)
shell: powershell
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-testmondata-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ jobs:
env:
TESTS_VERSION: ${{ env.TESTS_VERSION }}
run: |
uv sync --frozen --group tests --extra all
uv sync --frozen --group tests --extra all-dotnet

- name: Restore testmondata cache
uses: ./.github/actions/testmon-cache
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/weekly-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,7 @@ jobs:
env:
TESTS_VERSION: ${{ env.TESTS_VERSION }}
run: |
uv sync --frozen --group tests --extra all
uv sync --frozen --group tests --extra all-dotnet


- name: Remove Ansys processes (if any)
Expand Down
1 change: 1 addition & 0 deletions doc/changelog.d/7861.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test_export_to_aedt_test_FAIL_New
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -449,4 +449,3 @@ showcontent = true
directory = "test"
name = "Test"
showcontent = true

4 changes: 2 additions & 2 deletions src/ansys/aedt/core/application/design.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ def odesign(self) -> object:
>>> app.odesign

"""
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)
Expand Down Expand Up @@ -1521,7 +1521,7 @@ def oproject(self) -> _OProject:
>>> app.oproject

"""
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 cast(_OProject, self._oproject)

Expand Down
2 changes: 1 addition & 1 deletion src/ansys/aedt/core/desktop.py
Original file line number Diff line number Diff line change
Expand Up @@ -1435,7 +1435,7 @@ def odesktop(self) -> object:
>>> d.odesktop

"""
if settings.use_grpc_api:
if self.is_grpc_api:
Comment thread
Samuelopez-ansys marked this conversation as resolved.
tries = 0
while tries < 5:
try:
Expand Down
33 changes: 20 additions & 13 deletions src/ansys/aedt/core/filtersolutions.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,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
Expand Down Expand Up @@ -137,16 +133,27 @@ 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
original_use_grpc = settings.use_grpc_api
try:
settings.aedt_version = desktop_version
# FilterSolutions starts AEDT in COM mode; force COM when reconnecting.
settings.use_grpc_api = False
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
settings.use_grpc_api = original_use_grpc


class LumpedDesign(FilterDesignBase):
Expand Down
6 changes: 5 additions & 1 deletion src/ansys/aedt/core/filtersolutions_core/dll_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,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
Expand Down
18 changes: 9 additions & 9 deletions src/ansys/aedt/core/filtersolutions_core/export_to_aedt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1758,15 +1758,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
Expand Down Expand Up @@ -1801,15 +1801,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
Expand Down Expand Up @@ -1844,15 +1844,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
Expand Down
87 changes: 85 additions & 2 deletions tests/system/filter_solutions/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,22 @@
# SOFTWARE.

import locale
import time

import psutil
import pytest

from ansys.aedt.core import settings
from ansys.aedt.core.filtersolutions import DistributedDesign
from ansys.aedt.core.filtersolutions import FilterDesignBase
from ansys.aedt.core.filtersolutions import LumpedDesign
import ansys.aedt.core.filtersolutions_core as filtersolutions_core
from tests.conftest import DESKTOP_VERSION

# Filter Solutions export attaches PyAEDT to the AEDT process started by the DLL.
# That session is typically COM-based (no gRPC port), so force COM for reconnection
# even when the global test config has use_grpc enabled.
settings.use_grpc_api = False
# Ensure US English locale (with cross-platform fallbacks)
for _loc in ("en_US.UTF-8", "English_United States.1252", "en_US"):
try:
Expand All @@ -42,13 +51,87 @@
locale.setlocale(locale.LC_ALL, "C")


def _wait_for_process_exit(process_id: int, timeout: float = 30.0) -> None:
"""Wait until an AEDT process has fully exited."""
try:
psutil.Process(process_id).wait(timeout=timeout)
except (psutil.NoSuchProcess, psutil.TimeoutExpired):
pass


def _terminate_aedt_processes(*process_ids: int) -> None:
"""Terminate AEDT processes started by FilterSolutions export."""
for process_id in process_ids:
if not process_id:
continue
try:
psutil.Process(process_id).kill()
except psutil.NoSuchProcess:
pass
_wait_for_process_exit(process_id)


def _reinitialize_filtersolutions_dll() -> None:
"""Reinitialize the in-process FilterSolutions DLL after an AEDT export session."""
dll = filtersolutions_core._internal_dll_interface
if dll is not None:
dll.restore_defaults()
FilterDesignBase._active_design = None


def _running_aedt_process_ids() -> list[int]:
process_ids = []
for proc in psutil.process_iter(["pid", "exe", "name"]):
if _is_aedt_process(proc):
process_ids.append(proc.info["pid"])
return process_ids


def prepare_filtersolutions_for_export() -> None:
"""Ensure no stale AEDT export sessions interfere with the next export test."""
_terminate_aedt_processes(*_running_aedt_process_ids())
_reinitialize_filtersolutions_dll()
time.sleep(1)


def _is_aedt_process(process: psutil.Process) -> bool:
try:
exe = (process.info.get("exe") or "").lower()
except (psutil.NoSuchProcess, psutil.AccessDenied):
return False
return "ansys inc" in exe or "ansysem" in exe


def release_exported_design(design_app) -> None:
"""Detach PyAEDT from an exported design without corrupting the FilterSolutions DLL.

``close_desktop()`` shuts down AEDT while the DLL still holds references to that
session, which leads to access violations and AEDT communications failures in
subsequent tests. This helper releases the PyAEDT connection, terminates the
exported AEDT process, and reinitializes the DLL through ``restore_defaults()``.
"""
process_id = design_app.desktop_class.aedt_process_id
try:
design_app.desktop_class.release_desktop(close_projects=False, close_on_exit=False)
except Exception:
pass
_terminate_aedt_processes(process_id)
_reinitialize_filtersolutions_dll()


@pytest.fixture
def lumped_design():
"""Fixture for creating a LumpedDesign object."""
return LumpedDesign(DESKTOP_VERSION)
settings.use_grpc_api = False
design = LumpedDesign(DESKTOP_VERSION)
yield design
design.close()


@pytest.fixture
def distributed_design():
"""Fixture for creating a DistributedDesign object."""
return DistributedDesign(DESKTOP_VERSION)
settings.use_grpc_api = False
design = DistributedDesign(DESKTOP_VERSION)
yield design
design.close()
Loading
Loading